home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 5 / MacMania 5.toast / / Internet software / NewsWatcher / NW Source / Source / article.c < prev    next >
Text File  |  1997-01-09  |  112KB  |  4,175 lines

  1. /*----------------------------------------------------------------------------
  2.  
  3.     article.c
  4.  
  5.     This module handles article windows.
  6.     
  7.     Copyright © 1994-1997, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <ctype.h>
  15.  
  16. #include <Icons.h>
  17.  
  18. #include "glob.h"
  19. #include "article.h"
  20. #include "child.h"
  21. #include "newswatcher.h"
  22. #include "dialog.h"
  23. #include "header.h"
  24. #include "mark.h"
  25. #include "news.h"
  26. #include "menus.h"
  27. #include "datetime.h"
  28. #include "print.h"
  29. #include "next.h"
  30. #include "message.h"
  31. #include "cancel.h"
  32. #include "status.h"
  33. #include "strutil.h"
  34. #include "tescroll.h"
  35. #include "url.h"
  36. #include "drawutil.h"
  37. #include "memutil.h"
  38. #include "windutil.h"
  39. #include "teutil.h"
  40. #include "wind.h"
  41. #include "fileutil.h"
  42. #include "sfutil.h"
  43. #include "iconutil.h"
  44. #include "apputil.h"
  45. #include "dragutil.h"
  46. #include "subject.h"
  47. #include "group.h"
  48. #include "key.h"
  49. #include "search.h"
  50. #include "ic.h"
  51. #include "help.h"
  52. #include "arrowpair.h"
  53. #include "biglist.h"
  54.  
  55.  
  56.  
  57. #define kMinWindowWidth        400            /* minimum window width */
  58. #define kSectionMargin        165            /* right coord of "Section x of y" rect */
  59.  
  60. #define kArticleFileExistsAlert        144
  61. #define kTooStupidAlert                152
  62.  
  63. #define kFileIconID            203
  64.  
  65. #define kMaxCachedArticles                50            /* max number of cached articles */
  66. #define kArticleCacheMemoryToKeepFree    150000L        /* amount of memory to keep free when
  67.                                                        caching articles */
  68.  
  69.  
  70.  
  71. typedef struct TCachedArticleInfo {
  72.     unsigned long timeCached;    /* tickcount when entry added to cache */
  73.     Handle groupName;            /* handle to group name, or nil if by id */
  74.     long number;                /* article number in group if not by id */
  75.     Handle msgId;                /* handle to message id if by id */
  76.     Handle txt;                    /* handle to full article text (purgable!) */
  77.     Boolean inUse;                /* true if this cache entry is being used */
  78.     Boolean flagReqd;            /* true if when fetched from server BinHex or uuencode
  79.                                    text was required to have a special "begin" flag line */
  80.     Boolean attachedFile;        /* true if article contains an attached file */
  81. } TCachedArticleInfo;
  82.  
  83.  
  84.  
  85. static TCachedArticleInfo **gArticleCache = nil;    /* handle to article cache array */
  86.  
  87. static TEClickLoopUPP gAutoScrollUPP;
  88. static DragSendDataUPP gDragAttachedFileIconSendProcUPP;
  89. static ControlActionUPP gScrollActionUPP;
  90. static ControlActionUPP gScrollActionSectionUPP;
  91.  
  92.  
  93.  
  94. /*----------------------------------------------------------------------------
  95.     SaveArticleInCache 
  96.     
  97.     Save an article in the article cache.
  98.             
  99.     Entry:    wind = pointer to article window.
  100.     
  101.     When an article window is closed or reused, instead of disposing the article
  102.     text, we cache it in purgable memory in case the user reopens the article
  103.     in the near future (e.g., via the up arrow control in an article window).
  104.     
  105.     This function transfers ownership of the full article text relocatable block
  106.     from the article window to the cache, and the block is made purgable. 
  107.     Similarly, ownership of the two relocatable blocks containing the article 
  108.     message id and/or group name are transfered from the window to the cache,
  109.     but these blocks are not made purgable.
  110. ----------------------------------------------------------------------------*/
  111.  
  112. static void SaveArticleInCache (WindowPtr wind)
  113. {
  114.     TWindow **info;
  115.     OSErr err = noErr;
  116.     short i, oldestTimeCachedIndex;
  117.     Handle groupName, msgId, txt;
  118.     unsigned long oldestTimeCached;
  119.     TCachedArticleInfo *p;
  120.  
  121.     info = (TWindow**)GetWRefCon(wind);
  122.     
  123.     /* Do some sanity checks. */
  124.     
  125.     if ((**info).fullText == nil) goto exit;
  126.     if ((**info).msgId == nil && (**info).groupName == nil) goto exit;
  127.     
  128.     /* Allocate the cache info array on the first call. */
  129.     
  130.     if (gArticleCache == nil) {
  131.         err = MyNewHandle(kMaxCachedArticles * sizeof(TCachedArticleInfo), &gArticleCache);
  132.         if (err != noErr) goto exit;
  133.     }
  134.     
  135.     /* Search the cache to find either an available unused slot or the oldest used slot.
  136.        Treat any used slots with purged text as unused. */
  137.     
  138.     oldestTimeCached = 0xffffffff;
  139.     oldestTimeCachedIndex = -1;
  140.     for (i = 0, p = *gArticleCache; i < kMaxCachedArticles; i++, p++) {
  141.         if (p->inUse && *p->txt != nil) {
  142.             if (p->timeCached < oldestTimeCached) {
  143.                 oldestTimeCached = p->timeCached;
  144.                 oldestTimeCachedIndex = i;
  145.             }
  146.         } else {
  147.             break;
  148.         }
  149.     }
  150.     if (i >= kMaxCachedArticles) i = oldestTimeCachedIndex; 
  151.     
  152.     /* Dispose any old entry which occupies the slot. */
  153.     
  154.     p = *gArticleCache + i;
  155.     if (p->inUse) {
  156.         groupName = p->groupName;
  157.         msgId = p->msgId;
  158.         txt = p->txt;
  159.         MyDisposeHandle(groupName);
  160.         MyDisposeHandle(msgId);
  161.         MyDisposeHandle(txt);
  162.     }
  163.     
  164.     /* Cache the article at slot "i" in the cache info array. Make the
  165.        article text purgable. */
  166.     
  167.     p = *gArticleCache + i;
  168.     p->timeCached = TickCount();
  169.     p->groupName = (**info).groupName;
  170.     p->number = (**info).number;
  171.     p->msgId = (**info).msgId;
  172.     p->txt = (**info).fullText;
  173.     p->inUse = true;
  174.     p->flagReqd = (**info).flagReqd;
  175.     p->attachedFile = (**info).attachedFile;
  176.     (**info).groupName = nil;
  177.     (**info).msgId = nil;
  178.     (**info).fullText = nil;
  179.     HPurge(p->txt);
  180.     
  181.     /* If we're getting low on free memory, purge the oldest cache entries.
  182.        This helps prevent filling up all the free memory with cached articles, 
  183.        which would make the Memory Manager thrash around purging blocks all 
  184.        the time (and most likely purging the wrong ones at that. */
  185.     
  186.     while (FreeMem() < kArticleCacheMemoryToKeepFree) {
  187.         oldestTimeCached = 0xffffffff;
  188.         oldestTimeCachedIndex = -1;
  189.         for (i = 0, p = *gArticleCache; i < kMaxCachedArticles; i++, p++) {
  190.             if (p->inUse && p->timeCached < oldestTimeCached) {
  191.                 oldestTimeCached = p->timeCached;
  192.                 oldestTimeCachedIndex = i;
  193.             }
  194.         }
  195.         if (oldestTimeCachedIndex < 0) break;
  196.         p = *gArticleCache + oldestTimeCachedIndex;
  197.         groupName = p->groupName;
  198.         msgId = p->msgId;
  199.         txt = p->txt;
  200.         p->inUse = false;
  201.         MyDisposeHandle(groupName);
  202.         MyDisposeHandle(msgId);
  203.         MyDisposeHandle(txt);
  204.     }
  205.  
  206.     return;
  207.     
  208. exit:
  209.  
  210.     MyDisposeHandle((**info).fullText);
  211.     (**info).fullText = nil;
  212.     MyDisposeHandle((**info).msgId);
  213.     (**info).msgId = nil;
  214.     MyDisposeHandle((**info).groupName);
  215.     (**info).groupName = nil;
  216. }
  217.  
  218.  
  219.  
  220. /*----------------------------------------------------------------------------
  221.     GetArticleFromCache 
  222.     
  223.     Get an article from the article cache.
  224.             
  225.     Entry:    groupName = name of group, or nil if fetching by message id.
  226.             number = article number. Ignored if fetching by message id.
  227.             id = message id string, including < and > delimiters. Ignored
  228.                 if fetching by article number.
  229.             flagReqd = true if BinHex or uuencode text
  230.                 must include special "begin" flag line.
  231.                 
  232.     Exit:    function result = true if article found in cache.
  233.             *txt = handle to article text.
  234.             *length = length of article text.
  235.             *attachedFile = true if article contains an attached file.
  236.             
  237.     If the article is found in the cache, ownership of the relocatable block
  238.     containing the full article text is transferred from the cache to the
  239.     caller, and the block is made non-purgable.
  240. ----------------------------------------------------------------------------*/
  241.  
  242. static Boolean GetArticleFromCache (char *groupName, long number, char *id, 
  243.     Boolean flagReqd, Handle *txt, long *length, Boolean *attachedFile)
  244. {
  245.     short i;
  246.     TCachedArticleInfo *p;
  247.     Boolean match;
  248.     Handle h1, h2;
  249.  
  250.     if (gArticleCache == nil) return false;
  251.     
  252.     /* Search the cache for the article. */
  253.     
  254.     for (i = 0, p = *gArticleCache; i < kMaxCachedArticles; i++, p++) {
  255.         if (p->inUse && *p->txt != nil) {
  256.             if (groupName != nil && p->groupName != nil) {
  257.                 match = MyStrEqual(groupName, *p->groupName) && number == p->number;
  258.             } else if (id != nil && p->msgId != nil) {
  259.                 match = MyStrEqual(id, *p->msgId);
  260.             } else {
  261.                 match = false;
  262.             }
  263.             match = match && flagReqd == p->flagReqd;
  264.             if (match) {
  265.                 /* We found the article in the cache. Transfer ownership of the 
  266.                 text to our caller, make the text nonpurgable, and free the cache slot. */
  267.                 *txt = p->txt;
  268.                 HNoPurge(*txt);
  269.                 *attachedFile = p->attachedFile;
  270.                 p->inUse = false;
  271.                 h1 = p->groupName;
  272.                 h2 = p->msgId;
  273.                 MyDisposeHandle(h1);
  274.                 MyDisposeHandle(h2);
  275.                 *length = MyGetHandleSize(*txt);
  276.                 return true;
  277.             }
  278.         }
  279.     }
  280.     
  281.     return false;
  282. }
  283.  
  284.  
  285.  
  286. /*----------------------------------------------------------------------------
  287.     Rot13Text 
  288.     
  289.     Rot13 text.
  290.             
  291.     Entry:    text = handle to text.
  292.             start = offset in text to start.
  293.             end = offset in text to end.
  294. ----------------------------------------------------------------------------*/
  295.  
  296. void Rot13Text (Handle text, long start, long end)
  297. {
  298.     char *p, *pEnd;
  299.     
  300.     for (p = *text + start, pEnd = *text + end; p < pEnd; p++)
  301.         if (isalpha(*p)) *p += (toupper(*p) > 'M') ? -13 : 13;
  302. }
  303.  
  304.  
  305.  
  306. /*----------------------------------------------------------------------------
  307.     GetTextRect 
  308.     
  309.     Compute the "text" rectangle of an article window
  310.             
  311.     Entry:    wind = pointer to article window.
  312.             
  313.     Exit:    *textRect = text rectangle.
  314.     
  315.     The "text" rectangle is the area of the window where the text is 
  316.     displayed: the window portrect, minus the panel area, minus the scroll
  317.     bars, minus the text margin.
  318. ----------------------------------------------------------------------------*/
  319.  
  320. static void GetTextRect (WindowPtr wind, Rect *textRect)
  321. {
  322.     TWindow **info;
  323.  
  324.     info = (TWindow**)GetWRefCon(wind);
  325.     *textRect = wind->portRect;
  326.     textRect->top += (**info).panelHeight;
  327.     textRect->bottom -= 15;
  328.     textRect->right -= 15;
  329.     InsetRect(textRect, kTextMargin, kTextMargin);
  330. }
  331.  
  332.  
  333.  
  334. /*----------------------------------------------------------------------------
  335.     FixHeight 
  336.     
  337.     Round down window height to an exact multiple of lines.
  338.             
  339.     Entry:    wind = pointer to article window.
  340.             *height = window height.
  341.             
  342.     Exit:    *height = adjusted window height
  343. ----------------------------------------------------------------------------*/
  344.  
  345. static void FixHeight (WindowPtr wind, short *height)
  346. {
  347.     TWindow **info;
  348.     short panelHeight, lineHeight, adjust;
  349.  
  350.     info = (TWindow**)GetWRefCon(wind);
  351.     panelHeight = (**info).panelHeight;
  352.     lineHeight = (**info).lineHeight;
  353.     adjust = panelHeight + 15 + 2*kTextMargin;
  354.     *height = (*height - adjust) / lineHeight * lineHeight + adjust;
  355. }
  356.  
  357.  
  358.  
  359. /*----------------------------------------------------------------------------
  360.     MinHeight 
  361.     
  362.     Compute the minimum height of an article window.
  363.             
  364.     Entry:    wind = pointer to article window.
  365. ----------------------------------------------------------------------------*/
  366.  
  367. static short MinHeight (WindowPtr wind)
  368. {
  369.     TWindow **info;
  370.     short lineHeight, height, extra;
  371.     
  372.     info = (TWindow**)GetWRefCon(wind);
  373.     lineHeight = (**info).lineHeight;
  374.     extra = lineHeight + 15 + 2*kTextMargin;
  375.     if (extra < 65) extra = 65 + lineHeight;
  376.     height = (**info).panelHeight + extra;
  377.     FixHeight(wind, &height);
  378.     return height;
  379. }
  380.  
  381.  
  382.  
  383. /*----------------------------------------------------------------------------
  384.     DrawSectionMessage
  385.     
  386.     Draw the "Section x of y" message in an article window.
  387.             
  388.     Entry:    wind = pointer to article window.
  389. ----------------------------------------------------------------------------*/
  390.  
  391. static void DrawSectionMessage (WindowPtr wind)
  392. {
  393.     TWindow **info;
  394.     short fontNum;
  395.     CStr255 msg;
  396.     CStr255 sectionMessageFormat;
  397.     TextStyle savedStyle;
  398.     short width, left;
  399.     
  400.     GetPortTextStyle(&savedStyle);
  401.     
  402.     info = (TWindow**)GetWRefCon(wind);
  403.     
  404.     MoveTo(16, wind->portRect.bottom - 15);
  405.     LineTo(16, wind->portRect.bottom);
  406.  
  407.     GetCString(kStrSectionMessageFormat, sectionMessageFormat);
  408.     GetFontNumber("\pMonaco", &fontNum);
  409.     TextFont(fontNum);
  410.     TextSize(9);
  411.     sprintf(msg, sectionMessageFormat, (**info).curSection+1, (**info).numSections);
  412.     c2pstr(msg);
  413.     width = StringWidth((StringPtr)msg);
  414.     left = 16 + ((kSectionMargin - 46 - width) >> 1);
  415.     MoveTo(left, wind->portRect.bottom-4);
  416.     DrawString((StringPtr)msg);
  417.     
  418.     DrawArrowPair((**info).sectionArrowPair);
  419.  
  420.     MoveTo(kSectionMargin - 30, wind->portRect.bottom - 15);
  421.     LineTo(kSectionMargin - 30, wind->portRect.bottom);
  422.     MoveTo(kSectionMargin - 15, wind->portRect.bottom - 15);
  423.     LineTo(kSectionMargin - 15, wind->portRect.bottom);
  424.     MoveTo(kSectionMargin, wind->portRect.bottom - 15);
  425.     LineTo(kSectionMargin, wind->portRect.bottom);
  426.     
  427.     SetPortTextStyle(&savedStyle);
  428. }
  429.  
  430.  
  431.  
  432. /*----------------------------------------------------------------------------
  433.     DrawPanelString
  434.     
  435.     Draw a string in the panel area of an article window.
  436.             
  437.     Entry:    wind = pointer to article window.
  438.             str = string to draw.
  439.             v = vertical coordinate of string.
  440.             width = max width of string.
  441. ----------------------------------------------------------------------------*/
  442.  
  443. static void DrawPanelString (char *str, short v, short width)
  444. {
  445.     if (width < 20) return;
  446.     c2pstr(str);
  447.     TruncString(width, (StringPtr)str, smTruncEnd);
  448.     MoveTo(10, v);
  449.     DrawString((StringPtr)str);
  450.     p2cstr((StringPtr)str);
  451. }
  452.  
  453.  
  454.  
  455. /*----------------------------------------------------------------------------
  456.     ScrollSection
  457.     
  458.     Scroll an article window horizontally (by sections).
  459.             
  460.     Entry:    wind = pointer to article window.
  461.             dh = number of sections to scroll.
  462. ----------------------------------------------------------------------------*/
  463.  
  464. static void ScrollSection (WindowPtr wind, short dh)
  465. {
  466.     TWindow **info;
  467.     TEHandle theTE;
  468.     short curSection, newSection;
  469.     Rect r, textRect;
  470.     Handle text;
  471.     long **breaks;
  472.     long offset, length;
  473.     char state;
  474.     
  475.     info = (TWindow**)GetWRefCon(wind);
  476.     theTE = (**info).theTE;
  477.     GetTextRect(wind, &textRect);
  478.     curSection = (**info).curSection;
  479.     (**info).curSection = newSection = curSection - dh;
  480.     r = wind->portRect;
  481.     r.top = r.bottom - 13;
  482.     r.left += kTextMargin + 15;
  483.     r.bottom -= 2;
  484.     r.right = kSectionMargin - 32;
  485.     EraseRect(&r);
  486.     DrawSectionMessage(wind);
  487.     EnableOrDisableArrowPair((**info).sectionArrowPair, -1, 
  488.         newSection > 0);
  489.     EnableOrDisableArrowPair((**info).sectionArrowPair, +1, 
  490.         newSection < (**info).numSections - 1);
  491.     text = (**info).fullText;
  492.     breaks = (**info).sectionBreaks;
  493.     offset = (*breaks)[newSection];
  494.     length = (*breaks)[newSection+1] - offset;
  495.     if (newSection == 0 && !(**info).showDetails) {
  496.         offset = FindBody(text);
  497.         if (offset > length) offset = length;
  498.         length -= offset;
  499.     }
  500.     state = MyHGetState(text);
  501.     MyHLock(text);
  502.     TESetText(*text+offset, length, theTE);
  503.     MyHSetState(text, state);
  504.     TESetSelect(0, 0, theTE);
  505.     SetControlValue((**info).vScroll,0);
  506.     (**theTE).destRect = textRect;
  507.     TEScrollAdjustScrollMax(theTE, (**info).vScroll);
  508.     InvalRect(&textRect);
  509.     HandleUpdate(wind);
  510.     KillBalloon();
  511. }
  512.  
  513.  
  514.  
  515. /*----------------------------------------------------------------------------
  516.     ScrollActionSection 
  517.     
  518.     Horizontal scroll bar action procedure (sections).
  519.             
  520.     Entry:    hScroll = handle to horizontal scroll bar control
  521.             part = part code.
  522. ----------------------------------------------------------------------------*/
  523.  
  524. static pascal void ScrollActionSection (ControlHandle hScroll, short part)
  525. {
  526.     WindowPtr wind;
  527.     TWindow **info;
  528.     short val, max, dh;
  529.     
  530.     wind = (**hScroll).contrlOwner;
  531.     info = (TWindow**)GetWRefCon(wind);
  532.     val = (**hScroll).contrlValue;
  533.     max = (**hScroll).contrlMax;
  534.     dh = 0;
  535.     switch (part) {
  536.         case inUpButton:
  537.         case inPageUp:
  538.             dh = val > 0 ? 1 : 0;
  539.             break;
  540.         case inDownButton:
  541.         case inPageDown:
  542.             dh = val < max ? -1 : 0;
  543.             break;
  544.         case kScrollToHome:
  545.             dh = val;
  546.             break;
  547.         case kScrollToEnd:
  548.             dh = val - max;
  549.             break;
  550.     }
  551.     if (dh != 0) {
  552.         SetControlValue(hScroll, val - dh);
  553.         ScrollSection(wind, dh);
  554.     }
  555. }
  556.  
  557.  
  558.  
  559. /*----------------------------------------------------------------------------
  560.     ScrollAction 
  561.     
  562.     Vertical scroll bar action proc.
  563.  
  564.     Entry:    vScroll = handle to vertical scroll bar control.
  565.             part = part code.
  566. ----------------------------------------------------------------------------*/
  567.  
  568. static pascal void ScrollAction (ControlHandle vScroll, short part)
  569. {
  570.     WindowPtr wind;
  571.     TWindow **info;
  572.     TEHandle theTE;
  573.     
  574.     wind = (**vScroll).contrlOwner;
  575.     info = (TWindow**)GetWRefCon(wind);
  576.     theTE = (**info).theTE;
  577.     TEScrollScrollByPartCode(theTE, vScroll, part);
  578. }
  579.  
  580.  
  581.  
  582. /*----------------------------------------------------------------------------
  583.     AutoScroll 
  584.     
  585.     Handle article window autoscrolling.
  586.             
  587.     Exit:    function result = true
  588. ----------------------------------------------------------------------------*/
  589.  
  590. static pascal Boolean AutoScroll (void)
  591. {
  592.     WindowPtr wind;
  593.     TWindow **info;
  594.     ControlHandle vScroll;
  595.     TEHandle theTE;
  596.  
  597.     wind = MyFrontWindow();
  598.     if (wind == nil) return true;
  599.     info = (TWindow**)GetWRefCon(wind);
  600.     vScroll = (**info).vScroll;
  601.     theTE = (**info).theTE;
  602.     TEScrollAutoScroll(theTE, vScroll);
  603.     return true;
  604. }
  605.  
  606.  
  607.  
  608. /*----------------------------------------------------------------------------
  609.     ResizeContents 
  610.     
  611.     Adjust an article window's contents after a window size change (grow
  612.     or zoom).
  613.             
  614.     Entry:    wind = pointer to article window.
  615. ----------------------------------------------------------------------------*/
  616.  
  617. static void ResizeContents (WindowPtr wind)
  618. {
  619.     TWindow **info;
  620.     short width, height, panelHeight;
  621.     ControlHandle hScroll, vScroll;
  622.     TEHandle theTE;
  623.     Rect r;
  624.     Point sectionArrowPairBotLeft, nextPrevArrowPairBotLeft;
  625.  
  626.     info = (TWindow**)GetWRefCon(wind);
  627.     panelHeight = (**info).panelHeight;
  628.     vScroll = (**info).vScroll;
  629.     hScroll = (**info).hScroll;
  630.     theTE = (**info).theTE;
  631.     width = wind->portRect.right;
  632.     height = wind->portRect.bottom;
  633.     
  634.     SetRect(&r, width-15, panelHeight-1, width+1, height-14);
  635.     (**vScroll).contrlRect = r;
  636.     
  637.     if (hScroll != nil) {
  638.         SetRect(&r, kSectionMargin, height-15, width-14, height+1);
  639.         (**hScroll).contrlRect = r;
  640.     }
  641.     
  642.     GetTextRect(wind, &r);
  643.     (**theTE).viewRect = (**theTE).destRect = r;
  644.     TECalText(theTE);
  645.     SetControlValue(vScroll, 0);
  646.     TEScrollAdjustScrollMax(theTE, vScroll);
  647.     TEScrollScrollSelectionIntoView(theTE, vScroll);
  648.     
  649.     if ((**info).sectionArrowPair != nil) {
  650.         SetPt(§ionArrowPairBotLeft, kSectionMargin - 26, height - 3);
  651.         MoveArrowPair((**info).sectionArrowPair, sectionArrowPairBotLeft);
  652.     }
  653.     
  654.     if ((**info).nextPrevArrowPair != nil) {
  655.         SetPt(&nextPrevArrowPairBotLeft, wind->portRect.right - 17, 30);
  656.         MoveArrowPair((**info).nextPrevArrowPair, nextPrevArrowPairBotLeft);
  657.     }
  658.     
  659.     InvalRect(&wind->portRect);
  660.     
  661.     RecordNewLockedWindowPosition(wind, &gPrefs.articleWindLocn);
  662. }
  663.  
  664.  
  665.  
  666. /*----------------------------------------------------------------------------
  667.     FindNextSectionBreak
  668.     
  669.     Find the next section break in an article
  670.     
  671.     Entry:    fullText = handle to full article text.
  672.             len = length of full article text.
  673.             offset = offset in full article text of previous break.
  674.             
  675.     Exit:    function result = offset in full article text of next break
  676. ----------------------------------------------------------------------------*/
  677.  
  678. static long FindNextSectionBreak (Handle fullText, long len, long offset)
  679. {
  680.     char *p, *pEnd, *qStart, *qEnd, *q, *s, *qCR, *qCRCR;
  681.  
  682.     p = *fullText + offset;
  683.     pEnd = *fullText + len;
  684.     qEnd = p + 31999;
  685.     if (qEnd > pEnd) qEnd = pEnd;
  686.     for (q = p; q < qEnd; q++) {
  687.         if (*q == FF) {
  688.             return q - *fullText + 1;
  689.         }
  690.     }
  691.     if (qEnd >= pEnd) return len;
  692.     qStart = p + 31999;
  693.     qCR = qCRCR = qEnd = p + 25010;
  694.     for (q = qStart; q > qEnd; q--) {
  695.         if (*q == CR) {
  696.             qCR = q;
  697.             if (*(q-1) == CR) {
  698.                 qCRCR = q;
  699.                 for (s = q-2; *s == '-' && s > qEnd; s--) /* do nothing */;
  700.                 if (*s == CR) break;
  701.             }
  702.         }
  703.     }
  704.     if (q <= qEnd) q = qCRCR;
  705.     if (q <= qEnd) q = qCR;
  706.     if (q <= qEnd) q = qStart;
  707.     return q - *fullText;
  708. }
  709.  
  710.  
  711.  
  712. /*----------------------------------------------------------------------------
  713.     MakeSections
  714.     
  715.     Split a large (>32K) block of text into sections.
  716.     
  717.     Entry:    text = handle to large block of text.
  718.     
  719.     Exit:    function result = error code.
  720.             *sectionBreaks = section breaks.
  721.             *numSections = number of sections.
  722. ----------------------------------------------------------------------------*/
  723.  
  724. OSErr MakeSections (Handle text, long ***sectionBreaks, short *numSections)
  725. {
  726.     long **breaks = nil;
  727.     long len, offset, breaksAllocated;
  728.     short n;
  729.     OSErr err = noErr;
  730.  
  731.     len = MyGetHandleSize(text);
  732.     err = MyNewHandle(10*sizeof(long), &breaks);
  733.     if (err != noErr) goto exit;
  734.     breaksAllocated = 10;
  735.     offset = 0;
  736.     n = 0;
  737.     while (offset < len) {
  738.         if (n+1 >= breaksAllocated) {
  739.             breaksAllocated += 10;
  740.             err = MySetHandleSize(breaks, breaksAllocated*sizeof(long));
  741.             if (err != noErr) goto exit;
  742.         }
  743.         (*breaks)[n++] = offset;
  744.         offset = FindNextSectionBreak(text, len, offset);
  745.     }
  746.     (*breaks)[n] = len;
  747.     MySetHandleSize(breaks, (n+1)*sizeof(long));
  748.     *numSections = n;
  749.     *sectionBreaks = breaks;
  750.     
  751.     return noErr;
  752.     
  753. exit:
  754.  
  755.     MyDisposeHandle(breaks);
  756.     return err;
  757. }
  758.  
  759.  
  760.  
  761. /*----------------------------------------------------------------------------
  762.     CalcSectionBreaks
  763.     
  764.     Calculate section breaks for an article larger than 32K and create the
  765.     horizontal scroll bar.
  766.     
  767.     Entry:    wind = pointer to article window.
  768.     
  769.     Exit:    function result = error code.
  770. ----------------------------------------------------------------------------*/
  771.  
  772. static OSErr CalcSectionBreaks (WindowPtr wind)
  773. {
  774.     TWindow **info;
  775.     Handle fullText;
  776.     long **breaks = nil;
  777.     short numSections;
  778.     Rect r;
  779.     OSErr err = noErr;
  780.  
  781.     info = (TWindow**)GetWRefCon(wind);
  782.     fullText = (**info).fullText;
  783.     err = MakeSections(fullText, &breaks, &numSections);
  784.     if (err != noErr) goto exit;
  785.     (**info).numSections = numSections;
  786.     (**info).curSection = 0;
  787.     (**info).sectionBreaks = breaks;
  788.     
  789.     if (numSections > 1) {
  790.         SetRect(&r, kSectionMargin, wind->portRect.bottom - 15,
  791.             wind->portRect.right - 14, wind->portRect.bottom + 1);
  792.         (**info).hScroll = NewControl(wind, &r, "\p", true, 0, 0,
  793.             numSections-1, scrollBarProc, 0);
  794.     }
  795.     
  796.     return noErr;
  797.     
  798. exit:
  799.  
  800.     MyDisposeHandle(breaks);
  801.     return err;
  802. }
  803.  
  804.  
  805.  
  806. /*----------------------------------------------------------------------------
  807.     ComputePanelInfo
  808.     
  809.     Compute information about the panel area of a new article window.
  810.     
  811.     Entry:    wind = pointer to article window.
  812.             txt = handle to full article text.
  813.                 
  814.     Exit:    function result = error code.
  815. ----------------------------------------------------------------------------*/
  816.  
  817. static OSErr ComputePanelInfo (WindowPtr wind, Handle txt)
  818. {
  819.     TWindow **info;
  820.     short lineHeight;
  821.     short panelHeight;
  822.     short numPanelLines;
  823.     Handle from = nil;
  824.     Handle newsgroups = nil;
  825.     Handle followupto = nil;
  826.     Handle replyto = nil;
  827.     long fromLen, newsgroupsLen, followuptoLen, replytoLen;
  828.     OSErr err = noErr;
  829.     
  830.     info = (TWindow**)GetWRefCon(wind);
  831.     lineHeight = (**info).lineHeight;
  832.     if (lineHeight < 11) lineHeight = 11;
  833.     
  834.     err = FindHeaderHandle(txt, "From", &from);
  835.     if (err != noErr) goto exit;
  836.     err = FindHeaderHandle(txt, "Newsgroups", &newsgroups);
  837.     if (err != noErr) goto exit;
  838.     err = FindHeaderHandle(txt, "Followup-To", &followupto);
  839.     if (err != noErr) goto exit;
  840.     err = FindHeaderHandle(txt, "Reply-To", &replyto);
  841.     if (err != noErr) goto exit;
  842.     
  843.     (**info).showReplyTo = (**info).showFollowupTo = false;
  844.     
  845.     if (from != nil && replyto != nil) {
  846.         fromLen = MyGetHandleSize(from);
  847.         replytoLen = MyGetHandleSize(replyto);
  848.         (**info).showReplyTo = fromLen != replytoLen ||
  849.             !MyStrNEqual(*from, *replyto, fromLen);
  850.     }
  851.     
  852.     if (newsgroups != nil && followupto != nil) {
  853.         newsgroupsLen = MyGetHandleSize(newsgroups);
  854.         followuptoLen = MyGetHandleSize(followupto);
  855.         (**info).showFollowupTo = newsgroupsLen != followuptoLen ||
  856.             !MyStrNEqual(*newsgroups, *followupto, newsgroupsLen);
  857.     }
  858.     
  859.     numPanelLines = 4;
  860.     if ((**info).showReplyTo) numPanelLines++;
  861.     if ((**info).showFollowupTo) numPanelLines++;
  862.     
  863.     panelHeight = numPanelLines*lineHeight + 9;
  864.     (**info).panelHeight = panelHeight;
  865.     
  866. exit:
  867.  
  868.     MyDisposeHandle(from);
  869.     MyDisposeHandle(newsgroups);
  870.     MyDisposeHandle(followupto);
  871.     MyDisposeHandle(replyto);
  872.     return err;
  873. }
  874.  
  875.  
  876.  
  877. /*----------------------------------------------------------------------------
  878.     CheckFirstLastArticle
  879.     
  880.     Check to see if an article is the first or last one in its parent subject
  881.     window.
  882.     
  883.     Entry:    wind = pointer to article window.
  884.     
  885.     Exit:    *first = true if article is first one.
  886.             *last = true if article is last one.
  887. ----------------------------------------------------------------------------*/
  888.  
  889. static void CheckFirstLastArticle (WindowPtr wind, Boolean *first, Boolean *last)
  890. {
  891.     TWindow **info, **parentInfo;
  892.     WindowPtr parentWindow;
  893.     TSubject **subjectArray;
  894.     long item, index, lastItem;
  895.     TSubject theSubject;
  896.     BigListRef subjectList;
  897.     
  898.     info = (TWindow**)GetWRefCon(wind);
  899.     parentWindow = (**info).parentWindow;
  900.     if (parentWindow == nil) return;
  901.     index = (**info).parentSubject;
  902.     parentInfo = (TWindow**)GetWRefCon(parentWindow);
  903.     subjectArray = (**parentInfo).subjectArray;
  904.     theSubject = (*subjectArray)[index];
  905.     subjectList = (**parentInfo).subjectList;
  906.     lastItem = BigLGetNumItems(subjectList) - 1;
  907.     if (theSubject.collapsed) {
  908.         item = FindParentItem(parentWindow, theSubject.threadHeadIndex);
  909.         *first = item == 0 && theSubject.threadOrdinal == 1;
  910.         *last = item == lastItem && theSubject.threadOrdinal == theSubject.threadLength;
  911.     } else {
  912.         item = FindParentItem(parentWindow, index);
  913.         *first = item == 0;
  914.         *last = item == lastItem;
  915.     }
  916. }
  917.  
  918.  
  919.  
  920. /*----------------------------------------------------------------------------
  921.     EnableOrDisableNextPrevArrows
  922.     
  923.     Enable/disable the next/prev article arrow controls in an article window.
  924.     
  925.     Entry:    wind = pointer to article window.
  926.     
  927.     The next article (down) arrow is enabled iff this article is not the 
  928.         last article in the parent subject window.
  929.     The prev article (up) arrow is enabled iff this article is not the 
  930.         first article in the parent subject window.
  931. ----------------------------------------------------------------------------*/
  932.  
  933. static void EnableOrDisableNextPrevArrows (WindowPtr wind)
  934. {
  935.     TWindow **info;
  936.     Boolean first, last;
  937.     ArrowPairRef nextPrevArrowPair;
  938.     
  939.     info = (TWindow**)GetWRefCon(wind);
  940.     nextPrevArrowPair = (**info).nextPrevArrowPair;
  941.     CheckFirstLastArticle(wind, &first, &last);
  942.     EnableOrDisableArrowPair(nextPrevArrowPair, -1, !first);
  943.     EnableOrDisableArrowPair(nextPrevArrowPair, +1, !last);
  944. }
  945.  
  946.  
  947. /*----------------------------------------------------------------------------
  948.     MakeNewWindow
  949.     
  950.     Create a new article window.
  951.     
  952.     Entry:    groupName = name of group, or nil if fetching by message id.
  953.             number = article number. Ignored if fetching by message id.
  954.             id = message id string, including < and > delimiters, or nil
  955.                 if article was fetched by article number.
  956.             parent = pointer to parent subject window, or nil if opening by id.
  957.             checkTitle = true if window title should contain checkmark.
  958.             reuse = pointer to article window to be reused, or nil to
  959.                 open new article window.
  960.             flagReqd = true if BinHex or uuencode text
  961.                 must include special "begin" flag line.
  962.             txt = handle to article text.
  963.             length = length of article text.
  964.             attachedFile = true if article contains attached file.
  965.                 
  966.     Exit:    function result = error code.
  967.             *theWindow = pointer to new article window.
  968. ----------------------------------------------------------------------------*/
  969.  
  970. static OSErr MakeNewWindow (char *groupName, long number,
  971.     char *id, WindowPtr parent, Boolean checkTitle, WindowPtr reuse,
  972.     Boolean flagReqd, Handle txt, long length, Boolean attachedFile,
  973.     WindowPtr *theWindow)
  974. {
  975.     short panelHeight, width, height;
  976.     WindowPtr wind = nil;
  977.     TWindow **info;
  978.     Rect r;
  979.     TEHandle theTE;
  980.     ControlHandle vScroll;
  981.     Handle fullText;
  982.     long offset;
  983.     OSErr err = noErr;
  984.     Handle msgId, groupNameHandle;
  985.     CStr255 title;
  986.     short len;
  987.     char state;
  988.     GrafPtr port;
  989.     short zoomDir;
  990.     Rect arrowRect, backwardHotRect, forwardHotRect;
  991.     ArrowPairRef sectionArrowPair, nextPrevArrowPair;
  992.     
  993.     MyICReadSharedPrefs(kICScreenFont);
  994.     
  995.     GetPort(&port);
  996.  
  997.     FindHeaderCString(txt, "Subject", title, sizeof(title));
  998.     c2pstr(title);
  999.     if (checkTitle) {
  1000.         len = (unsigned char)title[0];
  1001.         if (len == 255) len = 254;
  1002.         BlockMoveData(title + 1, title + 2, len);
  1003.         title[1] = checkMark;
  1004.         title[0] = len + 1;
  1005.     }
  1006.             
  1007.     if (reuse == nil) {
  1008.  
  1009.         err = CreateNewWindow(kArticle, (StringPtr)title, 
  1010.             gPrefs.textFont, gPrefs.textSize, &wind);
  1011.         if (err != noErr) goto exit;
  1012.         SetPort(wind);
  1013.         info = (TWindow**)GetWRefCon(wind);
  1014.         err = ComputePanelInfo(wind, txt);
  1015.         if (err != noErr) goto exit;
  1016.         panelHeight = (**info).panelHeight;
  1017.         width = kMinWindowWidth;
  1018.         height = MinHeight(wind);
  1019.         RestoreLockedWindowPosition(wind, width, height, gPrefs.articleWindPosLocked,
  1020.             &gPrefs.articleWindLocn);
  1021.         width = wind->portRect.right;
  1022.         height = wind->portRect.bottom;
  1023.         SetRect(&r, width-15, panelHeight-1, width+1, height-14); 
  1024.         vScroll = NewControl(wind, &r, "\p", false, 0, 0, 0, scrollBarProc, 1);
  1025.         (**info).vScroll = vScroll;
  1026.         zoomDir = inZoomOut;
  1027.         
  1028.     } else {
  1029.     
  1030.         SaveArticleInCache(reuse);
  1031.         wind = reuse;
  1032.         SetPort(wind);
  1033.         SetWTitle(wind, (StringPtr)title);
  1034.         InvalRect(&wind->portRect);
  1035.         info = (TWindow**)GetWRefCon(wind);
  1036.         err = ComputePanelInfo(wind, txt);
  1037.         if (err != noErr) goto exit;
  1038.         panelHeight = (**info).panelHeight;
  1039.         width = wind->portRect.right;
  1040.         height = wind->portRect.bottom;
  1041.         SetRect(&r, width-15, panelHeight-1, width+1, height-14);
  1042.         vScroll = (**info).vScroll;
  1043.         (**vScroll).contrlRect = r;
  1044.         SetControlValue(vScroll, 0);
  1045.         TEDispose((**info).theTE);
  1046.         (**info).theTE = nil;
  1047.         MyDisposeHandle((**info).sectionBreaks);
  1048.         (**info).sectionBreaks = nil;
  1049.         if ((**info).hScroll != nil) DisposeControl((**info).hScroll);
  1050.         (**info).hScroll = nil;
  1051.         DisposeArrowPair((**info).sectionArrowPair);
  1052.         (**info).sectionArrowPair = nil;
  1053.         DisposeArrowPair((**info).nextPrevArrowPair);
  1054.         (**info).nextPrevArrowPair = nil;
  1055.         RemoveChild(parent, wind);
  1056.         (**info).maxBodyLineWidth = 0;
  1057.         (**info).numBodyLines = 0;
  1058.         zoomDir = zoomOutOnlyIfBigger;
  1059.         
  1060.     }
  1061.         
  1062.     GetTextRect(wind, &r);
  1063.     theTE = TENew(&r, &r);
  1064.     (**theTE).clickLoop = gAutoScrollUPP;
  1065.     if (gHaveTEOutlineHilite) TEFeatureFlag(teFOutlineHilite, TEBitSet, theTE);
  1066.     
  1067.     (**info).theTE = theTE;
  1068.     (**info).parentWindow = parent;
  1069.     (**info).fullText = fullText = txt;
  1070.     txt = nil;
  1071.     (**info).showDetails = gPrefs.showArtHeaders;
  1072.     (**info).attachedFile = attachedFile;
  1073.     
  1074.     if (groupName == nil) {
  1075.         (**info).groupName = nil;
  1076.     } else {
  1077.         err = MyPtrToHand(groupName, &groupNameHandle, strlen(groupName) + 1);
  1078.         if (err != noErr) goto exit;
  1079.         (**info).groupName = groupNameHandle;
  1080.         (**info).number = number;
  1081.     }
  1082.     (**info).flagReqd = flagReqd;
  1083.     
  1084.     if (parent == nil) {
  1085.         err = MyPtrToHand(id, &msgId, strlen(id)+1);
  1086.         if (err != noErr) goto exit;
  1087.         (**info).msgId = msgId;
  1088.     } else {
  1089.         err = AddChild(parent, wind);
  1090.         if (err != noErr) goto exit;
  1091.     }
  1092.     
  1093.     err = CalcSectionBreaks(wind);
  1094.     if (err != noErr) goto exit;
  1095.     length = (*(**info).sectionBreaks)[1];
  1096.     
  1097.     offset = gPrefs.showArtHeaders ? 0 : FindBody(fullText);
  1098.     if (offset > length) offset = length;
  1099.     state = MyHGetState(fullText);
  1100.     MyHLock(fullText);
  1101.     err = MyTESetText(*fullText+offset, length-offset, theTE);
  1102.     MyHSetState(fullText, state);
  1103.     if (err != noErr) goto exit;
  1104.     
  1105.     TESetSelect(0, 0, theTE);
  1106.     
  1107.     if ((**info).numSections > 1) {
  1108.         SetRect(&arrowRect, kSectionMargin - 26, wind->portRect.bottom - 13,
  1109.             kSectionMargin - 4, wind->portRect.bottom - 3);
  1110.         SetRect(&backwardHotRect, kSectionMargin - 30, wind->portRect.bottom - 15,
  1111.             kSectionMargin - 15, wind->portRect.bottom);
  1112.         forwardHotRect = backwardHotRect;
  1113.         OffsetRect(&forwardHotRect, 15, 0);
  1114.         err = CreateArrowPair(false, &arrowRect, &backwardHotRect,
  1115.             &forwardHotRect, §ionArrowPair);
  1116.         if (err != noErr) goto exit;
  1117.         EnableOrDisableArrowPair(sectionArrowPair, -1, false);
  1118.         (**info).sectionArrowPair = sectionArrowPair;
  1119.     }
  1120.     
  1121.     if (parent != nil) {
  1122.         SetRect(&arrowRect, wind->portRect.right - 17, 8, wind->portRect.right - 5, 30);
  1123.         backwardHotRect = forwardHotRect = arrowRect;
  1124.         backwardHotRect.bottom = forwardHotRect.top = 21;
  1125.         backwardHotRect.top++;
  1126.         forwardHotRect.bottom++;
  1127.         err = CreateArrowPair(true, &arrowRect, &backwardHotRect,
  1128.             &forwardHotRect, &nextPrevArrowPair);
  1129.         if (err != noErr) goto exit;
  1130.         (**info).nextPrevArrowPair = nextPrevArrowPair;
  1131.     }
  1132.     
  1133.     if (!(**info).windPosLocked) {
  1134.         err = DoZoom(wind, zoomDir);
  1135.         if (err != noErr) goto exit;
  1136.     }
  1137.     
  1138.     TEScrollAdjustScrollMax(theTE, (**info).vScroll);
  1139.     
  1140.     if (reuse != nil) {
  1141.         if ((**info).hScroll != nil) ShowControl((**info).hScroll);
  1142.         TEActivate(theTE);
  1143.     }
  1144.     
  1145.     *theWindow = wind;
  1146.     SetPort(port);
  1147.     return noErr;
  1148.     
  1149. exit:
  1150.  
  1151.     MyDisposeHandle(txt);
  1152.     DoClose(wind);
  1153.     SetPort(port);
  1154.     return err;
  1155. }
  1156.  
  1157.  
  1158.  
  1159. /*----------------------------------------------------------------------------
  1160.     GetArticleAndMakeNewWindow
  1161.     
  1162.     Get an article from the news server and create a new article window.
  1163.     
  1164.     Entry:    host = address of news server, or nil to use default server.
  1165.             port = port number on news server if host not nil.
  1166.             groupName = name of group, or nil if fetching by message id.
  1167.             number = article number. Ignored if fetching by message id.
  1168.             id = message id string, including < and > delimiters. Ignored
  1169.                 if fetching by article number.
  1170.             parent = pointer to parent subject window, or nil if opening by id.
  1171.             checkTitle = true if window title should contain checkmark.
  1172.             reuse = pointer to article window to be reused, or nil to
  1173.                 open new article window.
  1174.             flagReqd = true if BinHex or uuencode text
  1175.                 must include special "begin" flag line.
  1176.                 
  1177.     Exit:    function result = error code.
  1178.             *theWindow = pointer to new article window, or nil if article
  1179.                 does not exist on server.
  1180. ----------------------------------------------------------------------------*/
  1181.  
  1182. static OSErr GetArticleAndMakeNewWindow (char *host, short port,
  1183.     char *groupName, long number, char *id, 
  1184.     WindowPtr parent, Boolean checkTitle, WindowPtr reuse, 
  1185.     Boolean flagReqd, WindowPtr *theWindow)
  1186. {
  1187.     Handle txt = nil;
  1188.     long length;
  1189.     OSErr err = noErr;
  1190.     Boolean attachedFile;
  1191.     
  1192.     if (host != nil || !GetArticleFromCache(groupName, number, id, 
  1193.         flagReqd, &txt, &length, &attachedFile))
  1194.     {
  1195.         err = GetArticle(host, port, groupName, number, id, "ARTICLE", true, 
  1196.             flagReqd, &txt, &length, &attachedFile);
  1197.         if (err != noErr) return err;
  1198.         if (txt == nil) {
  1199.             *theWindow = nil;
  1200.             return noErr;
  1201.         }
  1202.     }
  1203.     
  1204.     return MakeNewWindow(groupName, number, id, parent, checkTitle, reuse, 
  1205.         flagReqd, txt, length, attachedFile, theWindow);
  1206. }
  1207.  
  1208.  
  1209.  
  1210. /*----------------------------------------------------------------------------
  1211.     CheckAlreadyOpen
  1212.     
  1213.     Check to see if a referenced article window is already open. If it is,
  1214.     bring it to the front.
  1215.     
  1216.     Entry:    msgId = C-format message id of article.
  1217.                 
  1218.     Exit:    function result = true if window already open.
  1219. ----------------------------------------------------------------------------*/
  1220.  
  1221. static Boolean CheckAlreadyOpen (char *msgId)
  1222. {
  1223.     WindowPtr wind;
  1224.     TWindow **info;
  1225.     TWindowKind kind;
  1226.     
  1227.     wind = FrontWindow();
  1228.     while (wind != nil) {
  1229.         kind = GetMyWindowKind(wind);
  1230.         if (kind == kArticle) {
  1231.             info = (TWindow**)GetWRefCon(wind);
  1232.             if ((**info).msgId != nil) {
  1233.                 if (strcmp(msgId, *(**info).msgId) == 0) {
  1234.                     MySelectWindow(wind);
  1235.                     return true;
  1236.                 }
  1237.             }
  1238.         }
  1239.         wind = (WindowPtr)(((WindowPeek)wind)->nextWindow);
  1240.     }
  1241.     return false;
  1242. }
  1243.  
  1244.  
  1245.  
  1246. /*----------------------------------------------------------------------------
  1247.     OpenSubjectItem
  1248.     
  1249.     Open an article window corresponding to an item in a subject window.
  1250.     
  1251.     Entry:    wind = pointer to subject window.
  1252.             item = the item in the subject window to open.
  1253.             threadOrdinal = ordinal of article within thread to open if
  1254.                 theCell is a collapsed thread, else 1.
  1255.             reuse = pointer to article window to be reused, or nil to
  1256.                 open new article window.
  1257.                 
  1258.     Exit:    function result = error code.
  1259.             *child = pointer to opened article window, or nil if 
  1260.                 article does not exist on server.
  1261. ----------------------------------------------------------------------------*/
  1262.  
  1263. OSErr OpenSubjectItem (WindowPtr wind, long item, long threadOrdinal,
  1264.     WindowPtr reuse, WindowPtr *child)
  1265. {
  1266.     WindowPtr newWind = nil;
  1267.     TWindow **info, **newInfo;
  1268.     BigListRef subjectList;
  1269.     TSubject **subjectArray;
  1270.     CStr255 groupName;
  1271.     long number;
  1272.     OSErr err = noErr;
  1273.     Boolean flagReqd;
  1274.     long index;
  1275.     
  1276.     info = (TWindow**) GetWRefCon(wind);
  1277.     subjectList = (**info).subjectList;
  1278.     subjectArray = (**info).subjectArray;
  1279.     
  1280.     index = BigLGetData(subjectList, item);
  1281.     while (threadOrdinal-- > 1) 
  1282.         index = (*subjectArray)[index].nextInThread;
  1283.     
  1284.     if ((newWind = FindChildByIndex(wind, index)) != nil) {
  1285.         MySelectWindow(newWind);
  1286.         *child = newWind;
  1287.         return noErr;
  1288.     }
  1289.  
  1290.     number = (*subjectArray)[index].number;
  1291.     flagReqd = EncodedTextBeginLineIsRequired(subjectArray, index);
  1292.     
  1293.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  1294.     
  1295.     err = GetArticleAndMakeNewWindow(nil, 0, groupName, number, nil, wind, true, reuse, 
  1296.         flagReqd, &newWind);
  1297.     if (err != noErr) goto exit;
  1298.     
  1299.     if (newWind != nil) {
  1300.         newInfo = (TWindow**)GetWRefCon(newWind);
  1301.         (**newInfo).parentSubject = index;
  1302.         err = MarkArticle(newWind, true);
  1303.         if (err != noErr) goto exit;
  1304.         EnableOrDisableNextPrevArrows(newWind);
  1305.         if (reuse == nil) MyShowWindow(newWind);
  1306.     }
  1307.     
  1308.     *child = newWind;
  1309.     return noErr;
  1310.     
  1311. exit:
  1312.  
  1313.     DoClose(newWind);
  1314.     return err;
  1315. }
  1316.  
  1317.  
  1318.  
  1319. /*----------------------------------------------------------------------------
  1320.     OpenReferencedArticle
  1321.     
  1322.     Open a referenced article.
  1323.     
  1324.     Entry:    obj = message id or news URL of referenced article.
  1325.             
  1326.     Exit:    function result = error code (fnfErr if article not found).
  1327. ----------------------------------------------------------------------------*/
  1328.  
  1329. OSErr OpenReferencedArticle (CStr255 obj)
  1330. {
  1331.     CStr255 msgId;
  1332.     WindowPtr newWind = nil;
  1333.     OSErr err = noErr;
  1334.     
  1335.     if (MyStrNEqual(obj, "news:", 5)) {
  1336.         sprintf(msgId, "<%s>", obj+5);
  1337.     } else if (MyStrNEqual(obj, "<news:", 6)) {
  1338.         sprintf(msgId, "<%s", obj+6);
  1339.     } else {
  1340.         strcpy(msgId, obj);
  1341.     }
  1342.     
  1343.     if (CheckAlreadyOpen(msgId)) return noErr;
  1344.     
  1345.     err = GetArticleAndMakeNewWindow(nil, 0, nil, 0, msgId, nil, false, nil, true, &newWind);
  1346.     if (err != noErr) return err;
  1347.     if (newWind == nil) {
  1348.         return fnfErr;
  1349.     } else {
  1350.         MyShowWindow(newWind);
  1351.         return noErr;
  1352.     }
  1353. }
  1354.  
  1355.  
  1356.  
  1357. /*----------------------------------------------------------------------------
  1358.     OpenArticleOnAnyServer
  1359.     
  1360.     Open an article on any server, perhaps a different server from the
  1361.     default one (for opening nntp URLs).
  1362.     
  1363.     Entry:    host = address of news server.
  1364.             port = port number.
  1365.             newsgroup = newsgroup name.
  1366.             artNumber = article number.
  1367.             
  1368.     Exit:    function result = error code (fnfErr if article not found).
  1369. ----------------------------------------------------------------------------*/
  1370.  
  1371. OSErr OpenArticleOnAnyServer (char *host, short port, 
  1372.     char *newsgroup, long artNumber)
  1373. {
  1374.     WindowPtr newWind;
  1375.     OSErr err = noErr;
  1376.  
  1377.     err = GetArticleAndMakeNewWindow(host, port, newsgroup, artNumber, 
  1378.         nil, nil, false, nil, true, &newWind);
  1379.     if (err != noErr) return err;
  1380.     if (newWind == nil) {
  1381.         return fnfErr;
  1382.     } else {
  1383.         MyShowWindow(newWind);
  1384.         return noErr;
  1385.     }
  1386. }
  1387.  
  1388.  
  1389.  
  1390. /*----------------------------------------------------------------------------
  1391.     FormFileName 
  1392.     
  1393.     Form the default file name for saving an article.
  1394.             
  1395.     Entry:    wind = pointer to article window.
  1396.     
  1397.     Exit:    fileName = default file name.
  1398. ----------------------------------------------------------------------------*/
  1399.  
  1400. static void FormFileName (WindowPtr wind, Str31 fileName)
  1401. {
  1402.     Str255 title;
  1403.     
  1404.     GetWTitle(wind, title);
  1405.     if (title[0] > 0 && title[1] == checkMark) {
  1406.         title[0]--;
  1407.         BlockMoveData(title+2, title+1, title[0]);
  1408.     }
  1409.     MakeLegalFileName(title, fileName);
  1410. }
  1411.  
  1412.  
  1413.  
  1414. /*----------------------------------------------------------------------------
  1415.     PresentStandardArticleSaveFileDialog 
  1416.     
  1417.     Present the standard save file dialog.
  1418.             
  1419.     Entry:    fileName = default file name.
  1420.             *saveEncodedText = default value for save encoded text option.
  1421.             *saveThreadsToSeparateFiles = default value for save threads
  1422.                 to separate files option.
  1423.     
  1424.     Exit:    function result = error code.
  1425.             *fSpec = file spec.
  1426.             *scriptTag = script code.
  1427.             *saveEncodedText = true to save encoded text.
  1428.             *saveThreadsToSeparateFiles = true to save threads to separate
  1429.                 files.
  1430. ----------------------------------------------------------------------------*/
  1431.  
  1432. OSErr PresentStandardArticleSaveFileDialog (Str31 fileName, FSSpec *fSpec, 
  1433.     ScriptCode *scriptTag, Boolean *saveEncodedText,
  1434.     Boolean *saveThreadsToSeparateFiles)
  1435. {
  1436.     StandardFileReply reply;
  1437.     Str255 prompt;
  1438.     
  1439.     GetPString(kStrSaveArticlePrompt, prompt);
  1440.     MyStandardPutArticle(prompt, fileName, &reply, 
  1441.         saveEncodedText, saveThreadsToSeparateFiles,
  1442.         gPrefs.savedArtDefaultFolder ? gPrefs.savedArtDefaultFolderAlias : nil);
  1443.     if (!reply.sfGood) return userCanceledErr;
  1444.     *fSpec = reply.sfFile;
  1445.     *scriptTag = reply.sfScript;
  1446.     return noErr;
  1447. }
  1448.  
  1449.  
  1450.  
  1451. /*----------------------------------------------------------------------------
  1452.     PresentStandardArticleGetFileDialog 
  1453.     
  1454.     Present the standard get file dialog.
  1455.     
  1456.     Entry:    *saveEncodedText = default value of save encoded text option.
  1457.     
  1458.     Exit:    function result = error code.
  1459.             *fSpec = file spec.
  1460.             *saveEncodedText = true to save encoded text.
  1461. ----------------------------------------------------------------------------*/
  1462.  
  1463. OSErr PresentStandardArticleGetFileDialog (FSSpec *fSpec, Boolean *saveEncodedText)
  1464. {
  1465.     StandardFileReply reply;
  1466.     
  1467.     MyStandardAppendArticle(nil, 1, "TEXT", &reply, saveEncodedText, 
  1468.         gPrefs.savedArtDefaultFolder ? gPrefs.savedArtDefaultFolderAlias : nil);
  1469.     if (!reply.sfGood) return userCanceledErr;
  1470.     *fSpec = reply.sfFile;
  1471.     return noErr;
  1472. }
  1473.  
  1474.  
  1475.  
  1476. /*----------------------------------------------------------------------------
  1477.     SaveTextToArticleFile 
  1478.     
  1479.     Save text to a file.
  1480.             
  1481.     Entry:    text = handle to text to be saved.
  1482.             start = offset in text of first char to save.
  1483.             end = offset in text following last char char to save.
  1484.             fSpec = pointer to file spec.
  1485.             scriptTag = script code.
  1486.             append = true to append text to end of file.
  1487.     
  1488.     Exit:    function result = error code.
  1489. ----------------------------------------------------------------------------*/
  1490.  
  1491. static OSErr SaveTextToArticleFile (Handle text, long start, long end,
  1492.      FSSpec *fSpec, ScriptCode scriptTag, Boolean append)
  1493. {
  1494.     OSErr err = noErr;
  1495.     long length;
  1496.     short refNum = 0;
  1497.     char state;
  1498.     char *separator, *p;
  1499.     Boolean needCR, empty;
  1500.  
  1501.     state = MyHGetState(text);
  1502.  
  1503.     err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  1504.         scriptTag, append, &refNum, &empty);
  1505.     if (err != noErr) goto exit;
  1506.     
  1507.     if (append && !empty) {
  1508.         separator = 
  1509.             "\r----------------------------------------------------------------------\r\r";
  1510.         length = strlen(separator);
  1511.         err = MyFSWriteNoCache(refNum, &length, separator, GiveTime);
  1512.         if (err != noErr) goto exit;
  1513.     }
  1514.  
  1515.     length = end - start;
  1516.     MyHLock(text);
  1517.     
  1518.     for (p = *text + end - 1; p >= *text + start && *p == CR; p--) /* do nothing */;
  1519.     p++;
  1520.     if (p < *text + end) {
  1521.         needCR = false;
  1522.         length = p + 1 - *text - start;
  1523.     } else {
  1524.         needCR = true;
  1525.     }
  1526.     
  1527.     err = MyFSWriteNoCache(refNum, &length, *text + start, GiveTime);
  1528.     if (err != noErr) goto exit;
  1529.     if (needCR) {
  1530.         length = 1;
  1531.         err = MyFSWriteNoCache(refNum, &length, CRSTR, GiveTime);
  1532.         if (err != noErr) goto exit;
  1533.     }
  1534.     MyHSetState(text, state);
  1535.     MyFSClose(refNum, GiveTime);
  1536.     return noErr;
  1537.     
  1538. exit:
  1539.  
  1540.     MyHSetState(text, state);
  1541.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  1542.     return err;
  1543. }
  1544.  
  1545.  
  1546.  
  1547. /*----------------------------------------------------------------------------
  1548.     GetAndSaveFullTextToArticleFile 
  1549.     
  1550.     Get the full text of an article with attached binaries and save it 
  1551.     to a file.
  1552.             
  1553.     Entry:    wind = pointer to article window.
  1554.             fSpec = pointer to file spec.
  1555.             scriptTag = script code.
  1556.             append = true to append text to end of file.
  1557.     
  1558.     Exit:    function result = error code.
  1559. ----------------------------------------------------------------------------*/
  1560.  
  1561. static OSErr GetAndSaveFullTextToArticleFile (WindowPtr wind,
  1562.      FSSpec *fSpec, ScriptCode scriptTag, Boolean append)
  1563. {
  1564.     OSErr err = noErr;
  1565.     long length;
  1566.     short refNum = 0;
  1567.     char *separator;
  1568.     Boolean empty;
  1569.     TWindow **info, **parentInfo;
  1570.     WindowPtr parent;
  1571.     TSubject **subjectArray;
  1572.     short index;
  1573.     CStr255 groupName;
  1574.     long number;
  1575.     TAttachedFileKind partKind;
  1576.     Handle msgId;
  1577.     char state;
  1578.  
  1579.     err = DisplayStatusMessageNumber(kStrGettingAndSavingArticle);
  1580.     if (err != noErr) return err;
  1581.  
  1582.     err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  1583.         scriptTag, append, &refNum, &empty);
  1584.     if (err != noErr) goto exit;
  1585.     
  1586.     if (append && !empty) {
  1587.         separator = 
  1588.             "\r----------------------------------------------------------------------\r\r";
  1589.         length = strlen(separator);
  1590.         err = MyFSWriteNoCache(refNum, &length, separator, GiveTime);
  1591.         if (err != noErr) goto exit;
  1592.     }
  1593.     
  1594.     info = (TWindow**)GetWRefCon(wind);
  1595.     parent = (**info).parentWindow;
  1596.     
  1597.     if (parent == nil) {
  1598.     
  1599.         msgId = (**info).msgId;
  1600.         state = MyHGetState(msgId);
  1601.         MyHLockHi(msgId);
  1602.         err = CopyArticleToFile(nil, 0, *msgId, "ARTICLE", refNum,
  1603.             false, false, &partKind);
  1604.         MyHSetState(msgId, state);
  1605.         if (err != noErr) goto exit;
  1606.     
  1607.     } else {
  1608.     
  1609.         parentInfo = (TWindow**)GetWRefCon(parent);
  1610.         subjectArray = (**parentInfo).subjectArray;
  1611.         index = (**info).parentSubject;
  1612.         strcpy(groupName, *gGroupNames + (**parentInfo).groupNameOffset);
  1613.         number = (*subjectArray)[index].number;
  1614.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum,
  1615.             false, false, &partKind);
  1616.         if (err != noErr) goto exit;
  1617.     
  1618.     }
  1619.  
  1620.     MyFSClose(refNum, GiveTime);
  1621.     return noErr;
  1622.     
  1623. exit:
  1624.  
  1625.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  1626.     return err;
  1627. }
  1628.  
  1629.  
  1630.  
  1631. /*----------------------------------------------------------------------------
  1632.     CheckArticleFileExists 
  1633.     
  1634.     Check to see if an article file already exists in the default article
  1635.     file save directory. If it does, present an alert asking the user to
  1636.     append, replace, cancel, or pick a new name. If the "Append if file
  1637.     alread exists" preference is turned on, automatically append.
  1638.             
  1639.     Entry:    fileName = file name.
  1640.             *saveEncodedText = default value for save encoded text
  1641.                 option.
  1642.             *saveThreadsToSeparateFiles = default value for save threads
  1643.                 to separate files option.
  1644.     
  1645.     Exit:    function result = error code.
  1646.             *fSpec = file spec.
  1647.             *scriptTag = script code.
  1648.             *append = true to append.
  1649.             *saveEncodedText = true to save encoded text.
  1650.             *saveThreadsToSeparateFiles = true to save threads to 
  1651.                 separate files.
  1652. ----------------------------------------------------------------------------*/
  1653.  
  1654. OSErr CheckArticleFileExists (Str31 fileName, FSSpec *fSpec, 
  1655.     ScriptCode *scriptTag, Boolean *append, 
  1656.     Boolean *saveEncodedText, Boolean *saveThreadsToSeparateFiles)
  1657. {
  1658.     OSErr err = noErr;
  1659.     DialogPtr dlg = nil;
  1660.     Boolean valid;
  1661.     short item;
  1662.  
  1663.     *append = false;
  1664.     ValidateSavedFolderAlias(gPrefs.savedArtDefaultFolderAlias,
  1665.         &fSpec->vRefNum, &fSpec->parID, &valid);
  1666.     if (!valid) {
  1667.         ErrorMessageNumber(kStrDefaultArtFoldNotFound);
  1668.         err = PresentStandardArticleSaveFileDialog(fileName, fSpec, scriptTag, 
  1669.             saveEncodedText, saveThreadsToSeparateFiles);
  1670.         if (err != noErr) return err;
  1671.         return noErr;
  1672.     }
  1673.     *scriptTag = smSystemScript;
  1674.     CopyPascalString(fSpec->name, fileName);
  1675.     *append = gPrefs.appendIfFileAlreadyExists;
  1676.     err = FileOrFolderExists(fSpec);
  1677.     if (err == fnfErr) return noErr;
  1678.     if (err != noErr) return err;
  1679.     if (gPrefs.appendIfFileAlreadyExists) return noErr;
  1680.     *append = false;
  1681.     err = MyGetNewDialog(kArticleFileExistsAlert, 1, 2, &dlg);
  1682.     if (err != noErr) return err;
  1683.     ParamText(fileName, "\p", "\p", "\p");
  1684.     SetItemKeyEquivalent(dlg, 3, 'R');
  1685.     SetItemKeyEquivalent(dlg, 4, 'A');
  1686.     SetItemKeyEquivalent(dlg, 5, 'D');
  1687.     SysBeep(0);
  1688.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  1689.     if (item == 5) DlgFlashButton(dlg, 4);
  1690.     err = DoClose(dlg);
  1691.     if (err != noErr) return err;
  1692.     switch (item) {
  1693.         case 1: /* Pick a New Name */
  1694.             err = PresentStandardArticleSaveFileDialog(fileName, fSpec, 
  1695.                 scriptTag, saveEncodedText, saveThreadsToSeparateFiles);
  1696.             if (err != noErr) return err;
  1697.             return noErr;
  1698.         case 2: /* Cancel */
  1699.             return userCanceledErr;
  1700.         case 3: /* Replace */
  1701.             return noErr;
  1702.         case 4: /* Append */
  1703.         case 5: /* Append (off screen, Cmd-D equiv.) */
  1704.             *append = true;
  1705.             return noErr;
  1706.     }
  1707.     return noErr;
  1708. }
  1709.  
  1710.  
  1711.  
  1712. /*----------------------------------------------------------------------------
  1713.     SaveArticleWindToFile 
  1714.     
  1715.     Save article window text to a file.
  1716.             
  1717.     Entry:    wind = pointer to article window.
  1718.             modifiers = modifiers field from event record.
  1719.             fSpec = pointer to file spec.
  1720.             scriptTag = script code.
  1721.             append = true to append text to end of file.
  1722.             saveEncodedText = true to save encoded text.
  1723.     
  1724.     Exit:    function result = error code.
  1725. ----------------------------------------------------------------------------*/
  1726.  
  1727. static OSErr SaveArticleWindToFile (WindowPtr wind, short modifiers, 
  1728.     FSSpec *fSpec, ScriptCode scriptTag, Boolean append,
  1729.     Boolean saveEncodedText)
  1730. {
  1731.     TWindow **info;
  1732.     long start, end;
  1733.     Handle text;
  1734.     TEHandle theTE;
  1735.     Boolean shift;
  1736.     
  1737.     MyICReadSharedPrefs(kICeditorHelper);
  1738.     
  1739.     info = (TWindow**)GetWRefCon(wind);
  1740.     shift = (modifiers & shiftKey) != 0;
  1741.     
  1742.     if (!shift && saveEncodedText && (**info).attachedFile) {
  1743.     
  1744.         return GetAndSaveFullTextToArticleFile(wind, fSpec, scriptTag, append);
  1745.     
  1746.     } else {
  1747.     
  1748.         if (shift) {
  1749.             theTE = (**info).theTE;
  1750.             text = (**theTE).hText;
  1751.             start = (**theTE).selStart;
  1752.             end = (**theTE).selEnd;
  1753.         } else {
  1754.             text = (**info).fullText;
  1755.             start = 0;
  1756.             end = MyGetHandleSize(text);
  1757.         }
  1758.         return SaveTextToArticleFile(text, start, end, fSpec, scriptTag, append);
  1759.         
  1760.     }
  1761. }
  1762.  
  1763.  
  1764.  
  1765. /*----------------------------------------------------------------------------
  1766.     CheckForMissingParts 
  1767.     
  1768.     Check for missing parts before trying to extract binaries for an article
  1769.     window.
  1770.             
  1771.     Entry:    wind = pointer to article window.
  1772.     
  1773.     Exit:    function result = error code.
  1774. ----------------------------------------------------------------------------*/
  1775.  
  1776. static OSErr CheckForMissingParts (WindowPtr wind)
  1777. {
  1778.     TWindow **info, **parentInfo;
  1779.     WindowPtr parent;
  1780.     TSubject **subjectArray;
  1781.     short index;
  1782.     
  1783.     info = (TWindow**)GetWRefCon(wind);
  1784.     parent = (**info).parentWindow;
  1785.     parentInfo = (TWindow**)GetWRefCon(parent);
  1786.     subjectArray = (**parentInfo).subjectArray;
  1787.     index = (**info).parentSubject;
  1788.     if ((*subjectArray)[index].incomplete) {
  1789.         NoteMessageNumber(kStrDecodePartsMissing);
  1790.         return userCanceledErr;
  1791.     }
  1792.     return noErr;
  1793. }
  1794.  
  1795.  
  1796.  
  1797. /*----------------------------------------------------------------------------
  1798.     PresentStandardDownloadSaveFileDialog 
  1799.     
  1800.     Present the standard file dialog for the temp file for extracting
  1801.     binaries.
  1802.             
  1803.     Entry:    fileName = default file name.
  1804.     
  1805.     Exit:    function result = error code.
  1806.             *fSpec = file spec.
  1807.             *scriptTag = script code.
  1808. ----------------------------------------------------------------------------*/
  1809.  
  1810. OSErr PresentStandardDownloadSaveFileDialog (Str31 fileName, FSSpec *fSpec,
  1811.     ScriptCode *scriptTag)
  1812. {
  1813.     StandardFileReply reply;
  1814.     Str255 prompt;
  1815.     
  1816.     MyICReadSharedPrefs(kICDownloadFolder);
  1817.     
  1818.     GetPString(kStrDownloadTempFilePrompt, prompt); 
  1819.     MyStandardPutFile(prompt, fileName, &reply,
  1820.         gPrefs.savedBinDefaultFolder ? gPrefs.savedBinDefaultFolderAlias : nil);
  1821.     if (!reply.sfGood) return userCanceledErr;
  1822.     *fSpec = reply.sfFile;
  1823.     *scriptTag = reply.sfScript;
  1824.     return noErr;
  1825. }
  1826.  
  1827.  
  1828.  
  1829. /*----------------------------------------------------------------------------
  1830.     CheckDownloadFileExists 
  1831.     
  1832.     Check to see if a temp file already exists in the download directory. 
  1833.     If it does, make the temp file name unique.
  1834.             
  1835.     Entry:    fileName = default file name.
  1836.     
  1837.     Exit:    function result = error code.
  1838.             *fSpec = file spec.
  1839.             *scriptTag = script code.
  1840. ----------------------------------------------------------------------------*/
  1841.  
  1842. OSErr CheckDownloadFileExists (Str31 fileName, FSSpec *fSpec, 
  1843.     ScriptCode *scriptTag)
  1844. {
  1845.     OSErr err = noErr;
  1846.     Boolean valid;
  1847.     
  1848.     MyICReadSharedPrefs(kICDownloadFolder);
  1849.  
  1850.     ValidateSavedFolderAlias(gPrefs.savedBinDefaultFolderAlias,
  1851.         &fSpec->vRefNum, &fSpec->parID, &valid);
  1852.     if (!valid) {
  1853.         ErrorMessageNumber(kStrDownloadFoldNotFound);
  1854.         err = PresentStandardDownloadSaveFileDialog(fileName, fSpec, scriptTag);
  1855.         if (err != noErr) return err;
  1856.         return noErr;
  1857.     }
  1858.     *scriptTag = smSystemScript;
  1859.     CopyPascalString(fSpec->name, fileName);
  1860.     return MakeFileNameUnique(fSpec, nil);
  1861. }
  1862.  
  1863.  
  1864.  
  1865. /*----------------------------------------------------------------------------
  1866.     SaveBinariesToFile 
  1867.     
  1868.     Save binaries to a file for an article.
  1869.             
  1870.     Entry:    wind = pointer to subject window.
  1871.             index = index in subject array of article.
  1872.             fSpec = pointer to file spec.
  1873.             scriptTag = script code.
  1874.             artNum = article number being saved (1,2,3,...numArts).
  1875.             numArts = total number of articles being saved.
  1876.     
  1877.     Exit:    function result = error code.
  1878.             *fileKind = kind of attached file.
  1879. ----------------------------------------------------------------------------*/
  1880.  
  1881. static OSErr SaveBinariesToFile (WindowPtr wind, long index, FSSpec *fSpec,
  1882.     ScriptCode scriptTag, long artNum, long numArts, 
  1883.     TAttachedFileKind *fileKind)
  1884. {
  1885.     TWindow **info;
  1886.     TSubject **subjectArray;
  1887.     long numPartsToSave;
  1888.     CStr255 statusMsg, statusMsgFormat;
  1889.     Boolean append = false;
  1890.     long partNum, nextIndex, threadHeadIndex, n;
  1891.     Handle text = nil;
  1892.     long number;
  1893.     CStr255 groupName;
  1894.     Boolean empty, hasParts;
  1895.     OSErr err = noErr;
  1896.     short refNum;
  1897.     TAttachedFileKind partKind;
  1898.     long item;
  1899.     
  1900.     MyICReadSharedPrefs(kICeditorHelper);
  1901.     
  1902.     info = (TWindow**)GetWRefCon(wind);
  1903.     subjectArray = (**info).subjectArray;
  1904.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  1905.     
  1906.     *fileKind = kNoAttachedFile;
  1907.     
  1908.     threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  1909.     
  1910.     if ((*subjectArray)[index].numParts == kMaxLong) {
  1911.         hasParts = false;
  1912.         numPartsToSave = 1;
  1913.         partNum = 1;
  1914.     } else {
  1915.         hasParts = true;
  1916.         numPartsToSave = (*subjectArray)[index].numParts;
  1917.         index = threadHeadIndex;
  1918.     }
  1919.     
  1920.     if (numArts > 1) {
  1921.         GetCString(kStrGettingAndSavingArtAofBPartNofM, statusMsgFormat);
  1922.     } else if (hasParts) {
  1923.         GetCString(kStrGettingAndSavingPartNofM, statusMsgFormat);
  1924.     } else {
  1925.         GetCString(kStrGettingAndSavingFile, statusMsg);
  1926.     }
  1927.     
  1928.     err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  1929.         scriptTag, false, &refNum, &empty);
  1930.     if (err != noErr) return err;
  1931.     
  1932.     while (true) {
  1933.         if (hasParts) {
  1934.             while (index != -1 && (*subjectArray)[index].partNum == kMaxLong) 
  1935.                 index = (*subjectArray)[index].nextInThread;
  1936.             if (index == -1) break;
  1937.             partNum = (*subjectArray)[index].partNum;
  1938.             while (true) {
  1939.                 nextIndex = (*subjectArray)[index].nextInThread;
  1940.                 while (nextIndex != -1 && (*subjectArray)[nextIndex].partNum == kMaxLong) 
  1941.                     nextIndex = (*subjectArray)[nextIndex].nextInThread;
  1942.                 if (nextIndex == -1) break;
  1943.                 if ((*subjectArray)[nextIndex].partNum != partNum) break;
  1944.                 index = nextIndex;
  1945.             }
  1946.         }
  1947.         if (index == -1) break;
  1948.         if (numArts > 1) {
  1949.             sprintf(statusMsg, statusMsgFormat, artNum, numArts, partNum, numPartsToSave);
  1950.         } else if (hasParts) {
  1951.             sprintf(statusMsg, statusMsgFormat, partNum, numPartsToSave);
  1952.         }
  1953.         err = DisplayStatusMessage(statusMsg);
  1954.         if (err != noErr) goto exit;
  1955.         number = (*subjectArray)[index].number;
  1956.         
  1957.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum,
  1958.             false, false, &partKind);
  1959.         if (err != noErr) goto exit;
  1960.         if (partKind == kArtNotOnServer) {
  1961.             *fileKind = kArtNotOnServer;
  1962.             goto exit;
  1963.         } else if (*fileKind == kNoAttachedFile) {
  1964.             *fileKind = partKind;
  1965.         }
  1966.         if (!hasParts) break;
  1967.         index = (*subjectArray)[index].nextInThread;
  1968.         
  1969.     }
  1970.     
  1971.     if (*fileKind != kArtNotOnServer && *fileKind != kNoAttachedFile) {
  1972.         item = FindParentItem(wind, threadHeadIndex);
  1973.         if (item >= 0) {
  1974.             MarkSubjectItemRead(wind, item);
  1975.             if (!(*subjectArray)[threadHeadIndex].collapsed) {
  1976.                 n = (*subjectArray)[threadHeadIndex].threadLength;
  1977.                 while (--n > 0) {
  1978.                     item++;
  1979.                     MarkSubjectItemRead(wind, item);
  1980.                 }
  1981.             }
  1982.         }
  1983.     }
  1984.         
  1985. exit:
  1986.     
  1987.     MyFSClose(refNum, GiveTime);
  1988.     return err;
  1989. }
  1990.  
  1991.  
  1992.  
  1993. /*----------------------------------------------------------------------------
  1994.     RunDecodeHelperProgram 
  1995.     
  1996.     Run the helper program to decode the temp file.
  1997.             
  1998.     Entry:    fSpec = pointer to file spec for temp file.
  1999.             fileKind = kind of attached binary.
  2000.     
  2001.     Exit:    function result = error code.
  2002. ----------------------------------------------------------------------------*/
  2003.  
  2004. OSErr RunDecodeHelperProgram (FSSpec *fSpec, TAttachedFileKind fileKind)
  2005. {
  2006.     OSErr err = noErr;
  2007.     FSSpec appSpec, fSpecWithSuffix;
  2008.     Boolean running;
  2009.     ProcessSerialNumber psn;
  2010.     CStr255 fmt, msg;
  2011.     char *suffix;
  2012.     StringPtr helperName;
  2013.     
  2014.     helperName = fileKind == kBinHex ? gPrefs.hqxHelperName : gPrefs.uuHelperName;
  2015.  
  2016.     err = FindAppFromSig(fileKind == kBinHex ? gPrefs.hqxHelper : gPrefs.uuHelper, 
  2017.         &appSpec, &running, &psn);
  2018.     if (err != noErr) goto exit1;
  2019.     
  2020.     fSpecWithSuffix = *fSpec;
  2021.     suffix = fileKind == kBinHex ? ".hqx" : ".uu";
  2022.     err = MakeFileNameUnique(&fSpecWithSuffix, suffix);
  2023.     if (err != noErr) goto exit3;
  2024.     
  2025.     err = FSpRename(fSpec, fSpecWithSuffix.name);
  2026.     if (err != noErr) goto exit3;
  2027.     
  2028.     err = LaunchAppWithDoc(running, &appSpec, &psn, &fSpecWithSuffix, 0,
  2029.         launchContinue | launchNoFileFlags | launchDontSwitch | launchUseMinimum);
  2030.     if (err != noErr) goto exit2;
  2031.  
  2032.     return noErr;
  2033.     
  2034. exit1:
  2035.  
  2036.     if (err == fnfErr) {
  2037.         GetCString(kStrCantFindDecodeHelper, fmt);
  2038.         p2cstr(helperName);
  2039.         sprintf(msg, fmt, helperName);
  2040.         c2pstr((char*)helperName);
  2041.         DoCantFindHelperDialog(msg);
  2042.         return userCanceledErr;
  2043.     } else {
  2044.         goto exit3;
  2045.     }
  2046.     
  2047. exit2:
  2048.  
  2049.     if (err == memFullErr || err == memFragErr || err == appMemFullErr) {
  2050.         GetCString(kStrDecodeHelperNoMem, fmt);
  2051.         p2cstr(helperName);
  2052.         sprintf(msg, fmt, helperName);
  2053.         c2pstr((char*)helperName);
  2054.         ErrorMessage(msg);
  2055.         return userCanceledErr;
  2056.     } else {
  2057.         goto exit3;
  2058.     }
  2059.     
  2060. exit3:
  2061.  
  2062.     GetCString(kStrDecodeHelperUnexpectedErr, fmt);
  2063.     p2cstr(helperName);
  2064.     sprintf(msg, fmt, err, helperName);
  2065.     c2pstr((char*)helperName);
  2066.     ErrorMessage(msg);
  2067.     return userCanceledErr;
  2068. }
  2069.  
  2070.  
  2071.  
  2072. /*----------------------------------------------------------------------------
  2073.     ExtractBinaries 
  2074.     
  2075.     Extract binaries for an article or thread.
  2076.             
  2077.     Entry:    wind = pointer to subject window.
  2078.             index = index of article or part in subject array.
  2079.             artNum = article number being saved (1,2,3,...numArts).
  2080.             numArts = total number of articles being saved.
  2081.             modifiers = modifiers field from event record.
  2082.     
  2083.     Exit:    function result = error code.
  2084.             *fileKind = kind of attached file.
  2085. ----------------------------------------------------------------------------*/
  2086.  
  2087. OSErr ExtractBinaries (WindowPtr wind, short index, short artNum, 
  2088.     short numArts, short modifiers, TAttachedFileKind *fileKind)
  2089. {
  2090.     static Str31 fileName;
  2091.     static FSSpec fSpec;
  2092.     static ScriptCode scriptTag;
  2093.     OSErr err = noErr;
  2094.     
  2095.     MyICReadSharedPrefs(kICeditorHelper);
  2096.     
  2097.     if (artNum == 1) {
  2098.         GetPString(kStrTempFileName, fileName);
  2099.         if (gPrefs.savedBinDefaultFolder && (modifiers & optionKey) == 0) {
  2100.             err = CheckDownloadFileExists(fileName, &fSpec, &scriptTag);
  2101.             if (err != noErr) return err;
  2102.         } else {
  2103.             err = PresentStandardDownloadSaveFileDialog(fileName, &fSpec, &scriptTag);
  2104.             if (err != noErr) return err;
  2105.         }
  2106.     } else {
  2107.         CopyPascalString(fSpec.name, fileName);
  2108.         err = MakeFileNameUnique(&fSpec, nil);
  2109.         if (err != noErr) return err;
  2110.     }
  2111.     
  2112.     err = SaveBinariesToFile(wind, index, &fSpec, scriptTag, artNum, numArts,
  2113.         fileKind);
  2114.     if (err != noErr) goto exit;
  2115.     
  2116.     if (*fileKind == kNoAttachedFile || *fileKind == kArtNotOnServer) {
  2117.         FSpDelete(&fSpec);
  2118.         return noErr;
  2119.     }
  2120.     
  2121.     err = RunDecodeHelperProgram(&fSpec, *fileKind);
  2122.     if (err != noErr) return err;
  2123.     
  2124.     return noErr;
  2125.  
  2126. exit:
  2127.  
  2128.     FSpDelete(&fSpec);
  2129.     return err;
  2130. }
  2131.  
  2132.  
  2133.  
  2134. /*----------------------------------------------------------------------------
  2135.     TooStupidAlert 
  2136.     
  2137.     Present the "I'm too stupid to extract binaries" alert.
  2138.     
  2139.     Exit:    function result = error code (userCancledErr if alert
  2140.                 presented with no problems).
  2141. ----------------------------------------------------------------------------*/
  2142.  
  2143. static OSErr TooStupidAlert (void)
  2144. {
  2145.     OSErr err = noErr;
  2146.     DialogPtr dlg;
  2147.     short item;
  2148.     
  2149.     err = MyGetNewDialog(kTooStupidAlert, ok, 0, &dlg);
  2150.     if (err != noErr) return err;
  2151.     SysBeep(0);
  2152.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  2153.     err = DoClose(dlg);
  2154.     if (err != noErr) return err;
  2155.     return userCanceledErr;
  2156. }
  2157.  
  2158.  
  2159.  
  2160. /*----------------------------------------------------------------------------
  2161.     ExtractBinariesForArticleWindow 
  2162.     
  2163.     Extract binaries for an article window.
  2164.             
  2165.     Entry:    wind = pointer to article window.
  2166.             modifiers = modifiers field from event record.
  2167.     
  2168.     Exit:    function result = error code.
  2169. ----------------------------------------------------------------------------*/
  2170.  
  2171. static OSErr ExtractBinariesForArticleWindow (WindowPtr wind, short modifiers)
  2172. {
  2173.     TWindow **info;
  2174.     OSErr err = noErr;
  2175.     TAttachedFileKind fileKind;
  2176.     
  2177.     info = (TWindow**)GetWRefCon(wind);
  2178.     
  2179.     if ((**info).parentWindow == nil) return TooStupidAlert();
  2180.         
  2181.     err = CheckForMissingParts(wind);
  2182.     if (err != noErr) return err;
  2183.     
  2184.     err = ExtractBinaries((**info).parentWindow, (**info).parentSubject, 
  2185.         1, 1, modifiers, &fileKind);
  2186.     if (err != noErr) return err;
  2187.     
  2188.     if (fileKind == kNoAttachedFile) {
  2189.         NoteMessageNumber(kStrArtHasNoAttachedFile);
  2190.         return userCanceledErr;
  2191.     }
  2192.     
  2193.     if (fileKind == kArtNotOnServer) {
  2194.         NoteMessageNumber(kStrArtNotOnServer);
  2195.         return userCanceledErr;
  2196.     }
  2197.     
  2198.     return noErr;
  2199. }
  2200.  
  2201.  
  2202.  
  2203. /*----------------------------------------------------------------------------
  2204.     ExtractBinariesDragAndDrop 
  2205.     
  2206.     Extract binaries for an article window and the end of a drag
  2207.     and drop sequence.
  2208.             
  2209.     Entry:    wind = pointer to article window.
  2210.             fSpec = pointer to file spec.
  2211.     
  2212.     Exit:    function result = error code.
  2213. ----------------------------------------------------------------------------*/
  2214.  
  2215. static OSErr ExtractBinariesDragAndDrop (WindowPtr wind, FSSpec *fSpec)
  2216. {
  2217.     TWindow **info;
  2218.     OSErr err = noErr;
  2219.     TAttachedFileKind fileKind;
  2220.     
  2221.     info = (TWindow**)GetWRefCon(wind);
  2222.     
  2223.     err = CheckForMissingParts(wind);
  2224.     if (err != noErr) goto exit;
  2225.     
  2226.     err = SaveBinariesToFile((**info).parentWindow, (**info).parentSubject, 
  2227.         fSpec, smSystemScript, 1, 1, &fileKind);
  2228.     if (err != noErr) goto exit;
  2229.     
  2230.     if (fileKind == kNoAttachedFile) {
  2231.         NoteMessageNumber(kStrArtHasNoAttachedFile);
  2232.         err = userCanceledErr;
  2233.         goto exit;
  2234.     }
  2235.     
  2236.     if (fileKind == kArtNotOnServer) {
  2237.         NoteMessageNumber(kStrArtNotOnServer);
  2238.         return userCanceledErr;
  2239.     }
  2240.     
  2241.     err = RunDecodeHelperProgram(fSpec, fileKind);
  2242.     if (err != noErr) return err;
  2243.     
  2244.     return noErr;
  2245.  
  2246. exit:
  2247.  
  2248.     FSpDelete(fSpec);
  2249.     return err;
  2250. }
  2251.  
  2252.  
  2253.  
  2254. /*----------------------------------------------------------------------------
  2255.     CanExtractBinaries 
  2256.     
  2257.     Check to see if we can extract binaries from an article window (it has an
  2258.     attached binary file, or it is part 0 of a multiple-part posting).
  2259.             
  2260.     Entry:    wind = pointer to article window.
  2261.     
  2262.     Exit:    function result = true if can extract binaries from this window.
  2263.             *iconRect = rect enclosing attached file icon.
  2264. ----------------------------------------------------------------------------*/
  2265.  
  2266. static Boolean CanExtractBinaries (WindowPtr wind, Rect *iconRect)
  2267. {
  2268.     WindowPtr parent;
  2269.     TWindow **info, **parentInfo;
  2270.     TSubject **subjectArray;
  2271.     Boolean result;
  2272.     
  2273.     info = (TWindow**)GetWRefCon(wind);
  2274.     parent = (**info).parentWindow;
  2275.     if ((**info).attachedFile) {
  2276.         result = true;
  2277.     } else {
  2278.         if (parent == nil) {
  2279.             result = false;
  2280.         } else {
  2281.             parentInfo = (TWindow**)GetWRefCon(parent);
  2282.             subjectArray = (**parentInfo).subjectArray;
  2283.             result = (*subjectArray)[(**info).parentSubject].partNum == 0;
  2284.         }
  2285.     }
  2286.     if (result) {
  2287.         if (parent == nil) {
  2288.             SetRect(iconRect, wind->portRect.right - 32, 0, 
  2289.                 wind->portRect.right, 32);
  2290.             
  2291.         } else {
  2292.             SetRect(iconRect, wind->portRect.right - 44, 0,
  2293.                 wind->portRect.right - 12, 32);
  2294.         }
  2295.     }
  2296.     return result;
  2297. }
  2298.  
  2299.  
  2300.  
  2301. /*----------------------------------------------------------------------------
  2302.     DragAttachedFileIconSendProc 
  2303.     
  2304.     The Drag Manager send proc for dragging the attached file icon.
  2305.             
  2306.     Entry:    theType = flavor type = 'SPEC'.
  2307.             dragSendRefCon = pointer to file spec, with file name
  2308.                 filled in.
  2309.             theItemRef = item reference.
  2310.             theDragRef = drag reference.
  2311.     
  2312.     Exit:    function result = error code.
  2313.             file spec volume reference number and directory id filled in.
  2314.             file created.
  2315.             file spec sent to Finder as flavor data.
  2316. ----------------------------------------------------------------------------*/
  2317.  
  2318. static pascal OSErr DragAttachedFileIconSendProc (FlavorType theType,
  2319.     void *dragSendRefCon, ItemReference theItemRef, DragReference theDragRef)
  2320. {
  2321.     AEDesc dropLocation;
  2322.     OSErr err = noErr;
  2323.     FSSpec *fSpec;
  2324.     
  2325.     if (DragTargetWasTrash(theDragRef)) return userCanceledErr;
  2326.     
  2327.     MyICReadSharedPrefs(kICeditorHelper);
  2328.     
  2329.     fSpec = (FSSpec*)dragSendRefCon;
  2330.  
  2331.     err = GetDropLocation(theDragRef, &dropLocation);
  2332.     if (err != noErr) return err;
  2333.     
  2334.     err = GetDropLocationDirectory(&dropLocation, &fSpec->vRefNum, &fSpec->parID);
  2335.     if (err != noErr) goto exit;
  2336.     
  2337.     err = MakeFileNameUnique(fSpec, nil);
  2338.     if (err != noErr) goto exit;
  2339.  
  2340.     err = FSpCreate(fSpec, gPrefs.savedArtCreator, 'TEXT', smSystemScript);
  2341.     if (err != noErr) goto exit;
  2342.     
  2343.     err = SetDragItemFlavorData(theDragRef, theItemRef, 'SPEC', 
  2344.         fSpec, sizeof(FSSpec), 0);
  2345.  
  2346. exit:
  2347.  
  2348.     AEDisposeDesc(&dropLocation);
  2349.     return err;
  2350. }
  2351.  
  2352.  
  2353. /*----------------------------------------------------------------------------
  2354.     DragAttachedFileIcon 
  2355.     
  2356.     Handle a mouse down event in the attached file icon of an article 
  2357.     window when the Drag Manager is present.
  2358.             
  2359.     Entry:    wind = pointer to article window.
  2360.             iconRect = pointer to icon rectangle in local coords.
  2361.             modifiers = modifiers field from event record.
  2362.             
  2363.     Exit:    function result = error code.
  2364. ----------------------------------------------------------------------------*/
  2365.  
  2366. static OSErr DragAttachedFileIcon (WindowPtr wind, Rect *iconRect, short modifiers)
  2367. {
  2368.     OSErr err = noErr;
  2369.     DragReference dragRef;
  2370.     Boolean haveDragRef = false;
  2371.     PromiseHFSFlavor promise;
  2372.     RgnHandle dragRgn = nil;
  2373.     Rect globalIconRect;
  2374.     FSSpec fSpec;
  2375.     
  2376.     PlotIconID(iconRect, 0, ttSelected, kFileIconID);
  2377.     if (WaitMouseMoved(gCurEvent.where)) {
  2378.         err = NewDrag(&dragRef);
  2379.         if (err != noErr) goto exit;
  2380.         MyICReadSharedPrefs(kICeditorHelper);
  2381.         haveDragRef = true;
  2382.         promise.fileType = 'TEXT';
  2383.         promise.fileCreator = gPrefs.savedArtCreator;
  2384.         promise.fdFlags = 0;
  2385.         promise.promisedFlavor = 'SPEC';
  2386.         err = AddDragItemFlavor(dragRef, 1, flavorTypePromiseHFS, &promise, 
  2387.             sizeof(PromiseHFSFlavor), 0);
  2388.         if (err != noErr) goto exit;
  2389.         err = AddDragItemFlavor(dragRef, 1, 'SPEC', nil, 0, 0);
  2390.         if (err != noErr) goto exit;
  2391.         dragRgn = NewRgn();
  2392.         globalIconRect = *iconRect;
  2393.         LocalToGlobalRect(&globalIconRect);
  2394.         err = IconIDToRgn(dragRgn, &globalIconRect, ttNone, kFileIconID);
  2395.         if (err != noErr) goto exit;
  2396.         OutlineRegion(dragRgn);
  2397.         GetPString(kStrTempFileName, fSpec.name);
  2398.         err = SetDragSendProc(dragRef, gDragAttachedFileIconSendProcUPP, &fSpec);
  2399.         if (err != noErr) goto exit;
  2400.         err = TrackDrag(dragRef, &gCurEvent, dragRgn);
  2401.         if (err != noErr) goto exit;
  2402.         DisposeRgn(dragRgn);
  2403.         DisposeDrag(dragRef);
  2404.         PlotIconID(iconRect, 0, ttNone, kFileIconID);
  2405.         err = ExtractBinariesDragAndDrop(wind, &fSpec);
  2406.         if (err != noErr) goto exit;
  2407.     } else {
  2408.         PlotIconID(iconRect, 0, ttNone, kFileIconID);
  2409.          return ExtractBinariesForArticleWindow(wind, modifiers);
  2410.     }
  2411.     return noErr;
  2412.     
  2413. exit:
  2414.  
  2415.     if (haveDragRef) DisposeDrag(dragRef);
  2416.     if (dragRgn != nil) DisposeRgn(dragRgn);
  2417.     PlotIconID(iconRect, 0, ttNone, kFileIconID);
  2418.     return err;
  2419. }
  2420.  
  2421.  
  2422.  
  2423. /*----------------------------------------------------------------------------
  2424.     Find 
  2425.     
  2426.     Search an article window for a pattern.
  2427.             
  2428.     Entry:    wind = pointer to article window.
  2429.             offset = offset into currently displayed text section 
  2430.                 to begin search, or -1 to start search at beginning.
  2431.             gFindPattern = pattern.
  2432.     
  2433.     Exit:    function result = error code.
  2434. ----------------------------------------------------------------------------*/
  2435.  
  2436. static OSErr Find (WindowPtr wind, long offset)
  2437. {
  2438.     TWindow **info;
  2439.     Handle fullText;
  2440.     TEHandle theTE;
  2441.     short curSection, numSections, newSection;
  2442.     long **breaks;
  2443.     char state;
  2444.     long matchOffset, len;
  2445.     OSErr err = noErr;
  2446.     
  2447.     info = (TWindow**)GetWRefCon(wind);
  2448.     fullText = (**info).fullText;
  2449.     curSection = (**info).curSection;
  2450.     numSections = (**info).numSections;
  2451.     breaks = (**info).sectionBreaks;
  2452.     
  2453.     if (offset == -1) {
  2454.          offset = 0;
  2455.          if (!(**info).showDetails) offset += FindBody(fullText);
  2456.     } else {
  2457.         if (curSection == 0 && !(**info).showDetails)
  2458.             offset += FindBody(fullText);
  2459.         offset += (*breaks)[curSection];
  2460.     }
  2461.     len = (*breaks)[numSections] - offset;
  2462.     
  2463.     state = MyHGetState(fullText);
  2464.     MyHLock(fullText);
  2465.     err = MyNSubstringSearch(*fullText + offset, gFindPattern, len,
  2466.         &matchOffset, GiveTime);
  2467.     MyHSetState(fullText, state);
  2468.     if (err != noErr) return err;
  2469.     if (matchOffset == -1) {
  2470.         SysBeep(0);
  2471.     } else {
  2472.         offset += matchOffset;
  2473.         newSection = 0;
  2474.         while (offset >= (*breaks)[newSection+1]) newSection++;
  2475.         offset -= (*breaks)[newSection];
  2476.         if (newSection != curSection) {
  2477.             SetControlValue((**info).hScroll, newSection);
  2478.             ScrollSection(wind, curSection - newSection);
  2479.         }
  2480.         if (newSection == 0 && !(**info).showDetails)
  2481.             offset -= FindBody(fullText);
  2482.         theTE = (**info).theTE;
  2483.         TESetSelect(offset, offset + strlen(gFindPattern), theTE);
  2484.         TEScrollScrollToMiddle(theTE, offset, (**info).vScroll);
  2485.     }
  2486.     return noErr;
  2487. }
  2488.  
  2489.  
  2490.  
  2491. /*----------------------------------------------------------------------------
  2492.     DoSave 
  2493.     
  2494.     Handle the "Save" command.
  2495.             
  2496.     Entry:    wind = pointer to article window.
  2497.             modifiers = modifiers field from event record.
  2498.     
  2499.     Exit:    function result = error code.
  2500. ----------------------------------------------------------------------------*/
  2501.  
  2502. static OSErr DoSave (WindowPtr wind, short modifiers)
  2503. {
  2504.     Str31 fileName;
  2505.     FSSpec fSpec;
  2506.     ScriptCode scriptTag;
  2507.     Boolean append = false;
  2508.     OSErr err = noErr;
  2509.     TWindow **info;
  2510.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  2511.  
  2512.     info = (TWindow**)GetWRefCon(wind);
  2513.     saveEncodedText = gPrefs.saveEncodedText;
  2514.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  2515.     FormFileName(wind, fileName);
  2516.     if (gPrefs.savedArtDefaultFolder && (modifiers & optionKey) == 0) {
  2517.         err = CheckArticleFileExists(fileName, &fSpec, &scriptTag, &append,
  2518.             &saveEncodedText, &saveThreadsToSeparateFiles);
  2519.         if (err != noErr) return err;
  2520.     } else {
  2521.         err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, &scriptTag, 
  2522.             &saveEncodedText, &saveThreadsToSeparateFiles);
  2523.         if (err != noErr) return err;
  2524.     }
  2525.     return SaveArticleWindToFile(wind, modifiers, &fSpec, scriptTag, append,
  2526.         saveEncodedText);
  2527. }
  2528.  
  2529.  
  2530.  
  2531. /*----------------------------------------------------------------------------
  2532.     DoSaveAs
  2533.     
  2534.     Handle the "Save As" command.
  2535.             
  2536.     Entry:    wind = pointer to article window.
  2537.             modifiers = modifiers field from event record.
  2538.     
  2539.     Exit:    function result = error code.
  2540. ----------------------------------------------------------------------------*/
  2541.  
  2542. static OSErr DoSaveAs (WindowPtr wind, short modifiers)
  2543. {
  2544.     Str31 fileName;
  2545.     FSSpec fSpec;
  2546.     ScriptCode scriptTag;
  2547.     OSErr err = noErr;
  2548.     TWindow **info;
  2549.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  2550.  
  2551.     info = (TWindow**)GetWRefCon(wind);
  2552.     saveEncodedText = gPrefs.saveEncodedText;
  2553.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  2554.     FormFileName(wind, fileName);
  2555.     err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, 
  2556.         &scriptTag, &saveEncodedText, &saveThreadsToSeparateFiles);
  2557.     if (err != noErr) return err;
  2558.     return SaveArticleWindToFile(wind, modifiers, &fSpec, scriptTag, false,
  2559.         saveEncodedText);
  2560. }
  2561.  
  2562.  
  2563.  
  2564. /*----------------------------------------------------------------------------
  2565.     DoPrint 
  2566.     
  2567.     Handle the "Print" command.
  2568.             
  2569.     Entry:    wind = pointer to article window.
  2570.             modifiers = modifiers field from event record.
  2571.             
  2572.     Exit:    function result = error code.
  2573. ----------------------------------------------------------------------------*/
  2574.  
  2575. static OSErr DoPrint (WindowPtr wind, short modifiers)
  2576. {
  2577.     TWindow **info;
  2578.     TEHandle theTE;
  2579.     Handle text, fullText;
  2580.     CStr255 subject, from, str;
  2581.     OSErr err = noErr;
  2582.     long start, end;
  2583.     
  2584.     info = (TWindow**)GetWRefCon(wind);
  2585.     fullText = (**info).fullText;
  2586.     theTE = (**info).theTE;
  2587.     start = (**theTE).selStart;
  2588.     end = (**theTE).selEnd;
  2589.     
  2590.     err = StartPrint();
  2591.     if (err != noErr) return err;
  2592.     
  2593.     err = DisplayStatusMessageNumber(kStrPrinting);
  2594.     if (err != noErr) return err;
  2595.     
  2596.     FindHeaderCString(fullText, "Subject", subject, sizeof(subject));
  2597.     if (FindHeaderCString(fullText, "From", from, sizeof(from))) {
  2598.         FormatAuthorName(from);
  2599.         sprintf(str, "%.40s, %.100s", from, subject);
  2600.     } else {
  2601.         strcpy(str, subject);
  2602.     }
  2603.  
  2604.     if ((modifiers & shiftKey) == 0 || start >= end) { 
  2605.         text = fullText;
  2606.         start = 0;
  2607.         end = MyGetHandleSize(text);
  2608.     } else {
  2609.         text = (**theTE).hText;
  2610.     }
  2611.     
  2612.     return PrintText(text, start, end, str);
  2613. }
  2614.  
  2615.  
  2616.  
  2617. /*----------------------------------------------------------------------------
  2618.     DoAppend 
  2619.     
  2620.     Handle the "Append" command.
  2621.             
  2622.     Entry:    wind = pointer to article window.
  2623.             modifiers = modifiers field from event record.
  2624.     
  2625.     Exit:    function result = error code.
  2626. ----------------------------------------------------------------------------*/
  2627.  
  2628. static OSErr DoAppend (WindowPtr wind, short modifiers)
  2629. {
  2630.     FSSpec fSpec;
  2631.     OSErr err = noErr;
  2632.     TWindow **info;
  2633.     Boolean saveEncodedText;
  2634.     
  2635.     info = (TWindow**)GetWRefCon(wind);
  2636.     saveEncodedText = gPrefs.saveEncodedText;
  2637.     err = PresentStandardArticleGetFileDialog(&fSpec, &saveEncodedText);
  2638.     if (err != noErr) return err;
  2639.     return SaveArticleWindToFile(wind, modifiers, &fSpec, smSystemScript, true,
  2640.         saveEncodedText);
  2641. }
  2642.  
  2643.  
  2644.  
  2645. /*----------------------------------------------------------------------------
  2646.     DoCopy 
  2647.     
  2648.     Handle the "Copy" command.
  2649.             
  2650.     Entry:    wind = pointer to article window.
  2651. ----------------------------------------------------------------------------*/
  2652.  
  2653. static void DoCopy (WindowPtr wind)
  2654. {
  2655.     TWindow **info;
  2656.     TEHandle theTE;
  2657.     
  2658.     info = (TWindow**)GetWRefCon(wind);
  2659.     theTE = (**info).theTE;
  2660.     MyTECopy(theTE);
  2661. }
  2662.  
  2663.  
  2664.  
  2665. /*----------------------------------------------------------------------------
  2666.     DoSelectAll 
  2667.     
  2668.     Handle the "Select All" command.
  2669.             
  2670.     Entry:    wind = pointer to article window.
  2671. ----------------------------------------------------------------------------*/
  2672.  
  2673. static void DoSelectAll (WindowPtr wind)
  2674. {
  2675.     TWindow **info;
  2676.     TEHandle theTE;
  2677.     
  2678.     info = (TWindow**)GetWRefCon(wind);
  2679.     theTE = (**info).theTE;
  2680.     TESetSelect(0, kMaxShort, theTE);
  2681. }
  2682.  
  2683.  
  2684.  
  2685. /*----------------------------------------------------------------------------
  2686.     DoFind 
  2687.     
  2688.     Handle the "Find" command for an article window.
  2689.             
  2690.     Entry:    wind = pointer to article window.
  2691.     
  2692.     Exit:    function result = error code.
  2693. ----------------------------------------------------------------------------*/
  2694.  
  2695. static OSErr DoFind (WindowPtr wind)
  2696. {
  2697.     TWindow **info;
  2698.     OSErr err = noErr;
  2699.     TEHandle theTE;
  2700.     
  2701.     err = DoFindDialog();
  2702.     if (err != noErr) return err;
  2703.     info = (TWindow**)GetWRefCon(wind);
  2704.     theTE = (**info).theTE;
  2705.     return Find(wind, gPrefs.startFindAtBeginning ? -1 : (**theTE).selStart);
  2706. }
  2707.  
  2708.  
  2709.  
  2710. /*----------------------------------------------------------------------------
  2711.     DoFindAgain
  2712.     
  2713.     Handle the "Find Again" command for an article window.
  2714.             
  2715.     Entry:    wind = pointer to article window.
  2716.     
  2717.     Exit:    function result = error code.
  2718. ----------------------------------------------------------------------------*/
  2719.  
  2720. static OSErr DoFindAgain (WindowPtr wind)
  2721. {
  2722.     TWindow **info;
  2723.     TEHandle theTE;
  2724.     
  2725.     info = (TWindow**)GetWRefCon(wind);
  2726.     theTE = (**info).theTE;
  2727.     return Find(wind, (**theTE).selEnd);
  2728. }
  2729.  
  2730.  
  2731.  
  2732. /*----------------------------------------------------------------------------
  2733.     DoEnterSelection
  2734.     
  2735.     Handle the "Enter Selection" command for an article window.
  2736.             
  2737.     Entry:    wind = pointer to article window.
  2738.     
  2739.     Exit:    function result = error code.
  2740. ----------------------------------------------------------------------------*/
  2741.  
  2742. static OSErr DoEnterSelection (WindowPtr wind)
  2743. {
  2744.     TWindow **info;
  2745.     TEHandle theTE;
  2746.     short selStart, selEnd, len;
  2747.     Handle hText;
  2748.     
  2749.     info = (TWindow**)GetWRefCon(wind);
  2750.     theTE = (**info).theTE;
  2751.     selStart = (**theTE).selStart;
  2752.     selEnd = (**theTE).selEnd;
  2753.     hText = (**theTE).hText;
  2754.     if (selStart >= selEnd || selEnd > selStart + 255) return noErr;
  2755.     len = selEnd - selStart;
  2756.     BlockMoveData(*hText + selStart, gFindPattern, len);
  2757.     gFindPattern[len] = 0;
  2758.     return noErr;
  2759. }
  2760.  
  2761.  
  2762.  
  2763. /*----------------------------------------------------------------------------
  2764.     DoShowHideDetails 
  2765.     
  2766.     Handle the "Show/Hide Details" command.
  2767.             
  2768.     Entry:    wind = pointer to article window.
  2769.     
  2770.     Exit:    function result = error code.
  2771. ----------------------------------------------------------------------------*/
  2772.  
  2773. static OSErr DoShowHideDetails (WindowPtr wind)
  2774. {
  2775.     TWindow **info;
  2776.     Boolean showDetails;
  2777.     short curSection;
  2778.     TEHandle theTE;
  2779.     Handle text;
  2780.     long **breaks;
  2781.     long length, offset;
  2782.     Rect textRect;
  2783.     OSErr err = noErr;
  2784.     char state;
  2785.     
  2786.     info = (TWindow**)GetWRefCon(wind);
  2787.     theTE = (**info).theTE;
  2788.     showDetails = (**info).showDetails = !(**info).showDetails;
  2789.     SetEditMenuShowHideDetails(!showDetails);
  2790.     curSection = (**info).curSection;
  2791.     breaks = (**info).sectionBreaks;
  2792.     GetTextRect(wind, &textRect);
  2793.     
  2794.     if (curSection != 0) ScrollActionSection((**info).hScroll, kScrollToHome);
  2795.     TEScrollScrollByPartCode(theTE, (**info).vScroll, kScrollToHome);
  2796.     
  2797.     text = (**info).fullText;
  2798.     offset = 0;
  2799.     length = (*breaks)[1];
  2800.     if (!showDetails) {
  2801.         offset = FindBody(text);
  2802.         if (offset > length) offset = length;
  2803.         length -= offset;
  2804.     }
  2805.     state = MyHGetState(text);
  2806.     MyHLock(text);
  2807.     err = MyTESetText(*text + offset, length, theTE);
  2808.     MyHSetState(text, state);
  2809.     if (err != noErr) return err;
  2810.     TESetSelect(0, 0, theTE);
  2811.     TEScrollAdjustScrollMax(theTE, (**info).vScroll);
  2812.     InvalRect(&textRect);
  2813.  
  2814.     if (showDetails && gPrefs.reZoomWindows && 
  2815.         GetControlMaximum((**info).vScroll) > 0 && !(**info).windPosLocked) 
  2816.     {
  2817.         err = DoZoom(wind, inZoomOut);
  2818.         if (err != noErr) return err;
  2819.     } else {
  2820.         SetWindowNeedsZooming(wind);
  2821.     }
  2822.     return noErr;
  2823. }
  2824.  
  2825.  
  2826.  
  2827. /*----------------------------------------------------------------------------
  2828.     DoRot13 
  2829.     
  2830.     Handle the "Rot13" command.
  2831.             
  2832.     Entry:    wind = pointer to article window.
  2833. ----------------------------------------------------------------------------*/
  2834.  
  2835. static void DoRot13 (WindowPtr wind)
  2836. {
  2837.     TWindow **info;
  2838.     TEHandle theTE;
  2839.     Rect r;
  2840.     short selStart, selEnd, curSection;
  2841.     Boolean showDetails;
  2842.     Handle hText, text;
  2843.     long **breaks;
  2844.     long offset;
  2845.     
  2846.     info = (TWindow**)GetWRefCon(wind);
  2847.     showDetails = (**info).showDetails;
  2848.     theTE = (**info).theTE;
  2849.     curSection = (**info).curSection;
  2850.     text = (**info).fullText;
  2851.     breaks = (**info).sectionBreaks;
  2852.     selStart = (**theTE).selStart;
  2853.     selEnd = (**theTE).selEnd;
  2854.     if (selStart >= selEnd) {
  2855.         selStart = 0;
  2856.         selEnd = (**theTE).teLength;
  2857.     }
  2858.     hText = (**theTE).hText;
  2859.     offset = curSection == 0 ? 0 : (*breaks)[curSection];
  2860.     
  2861.     Rot13Text(hText, selStart, selEnd);
  2862.     r = (**theTE).viewRect;
  2863.     InvalRect(&r);
  2864.     
  2865.     if (curSection == 0 && !showDetails) offset += FindBody(text);
  2866.     BlockMoveData(*hText + selStart, *text + offset + selStart, selEnd - selStart);
  2867. }
  2868.  
  2869.  
  2870.  
  2871. /*----------------------------------------------------------------------------
  2872.     DoReply 
  2873.     
  2874.     Handle the "Reply" command.
  2875.             
  2876.     Entry:    wind = pointer to article window.
  2877.             modifiers = modifiers field from event record.
  2878.             
  2879.     Exit:    function result = error code.
  2880. ----------------------------------------------------------------------------*/
  2881.  
  2882. static OSErr DoReply (WindowPtr wind, short modifiers)
  2883. {
  2884.     TWindow **info;
  2885.     Handle text, quoteText;
  2886.     long start, end;
  2887.     TEHandle theTE;
  2888.         
  2889.     info = (TWindow**)GetWRefCon(wind);
  2890.     text = (**info).fullText;
  2891.     
  2892.     if ((modifiers & shiftKey) == 0) {
  2893.         quoteText = nil;
  2894.     } else {
  2895.         theTE = (**info).theTE;
  2896.         quoteText = (**theTE).hText;
  2897.         start = (**theTE).selStart;
  2898.         end = (**theTE).selEnd;
  2899.     }
  2900.     
  2901.     return OpenReplyWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
  2902. }
  2903.  
  2904.  
  2905.  
  2906. /*----------------------------------------------------------------------------
  2907.     DoForward 
  2908.     
  2909.     Handle the "Forward" command.
  2910.             
  2911.     Entry:    wind = pointer to article window.
  2912.             modifiers = modifiers field from event record.
  2913.             
  2914.     Exit:    function result = error code.
  2915. ----------------------------------------------------------------------------*/
  2916.  
  2917. static OSErr DoForward (WindowPtr wind, short modifiers)
  2918. {
  2919.     TWindow **info;
  2920.     Handle text, quoteText;
  2921.     long start, end;
  2922.     TEHandle theTE;
  2923.         
  2924.     info = (TWindow**)GetWRefCon(wind);
  2925.     text = (**info).fullText;
  2926.     
  2927.     if ((modifiers & shiftKey) == 0) {
  2928.         quoteText = nil;
  2929.     } else {
  2930.         theTE = (**info).theTE;
  2931.         quoteText = (**theTE).hText;
  2932.         start = (**theTE).selStart;
  2933.         end = (**theTE).selEnd;
  2934.     }
  2935.     
  2936.     return OpenForwardWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
  2937. }
  2938.  
  2939.  
  2940.  
  2941. /*----------------------------------------------------------------------------
  2942.     DoRedirect 
  2943.     
  2944.     Handle the "Redirect" command.
  2945.             
  2946.     Entry:    wind = pointer to article window.
  2947.             modifiers = modifiers field from event record.
  2948.             
  2949.     Exit:    function result = error code.
  2950. ----------------------------------------------------------------------------*/
  2951.  
  2952. static OSErr DoRedirect (WindowPtr wind, short modifiers)
  2953. {
  2954.     TWindow **info;
  2955.     Handle text, quoteText;
  2956.     long start, end;
  2957.     TEHandle theTE;
  2958.         
  2959.     info = (TWindow**)GetWRefCon(wind);
  2960.     text = (**info).fullText;
  2961.     
  2962.     if ((modifiers & shiftKey) == 0) {
  2963.         quoteText = nil;
  2964.     } else {
  2965.         theTE = (**info).theTE;
  2966.         quoteText = (**theTE).hText;
  2967.         start = (**theTE).selStart;
  2968.         end = (**theTE).selEnd;
  2969.     }
  2970.     
  2971.     return OpenRedirectWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
  2972. }
  2973.  
  2974.  
  2975.  
  2976. /*----------------------------------------------------------------------------
  2977.     DoExtractBinaries
  2978.     
  2979.     Handle the "Extract Binaries" command.
  2980.     
  2981.     Entry:    wind = pointer to article window.
  2982.             modifiers = modifiers field from event record.
  2983.     
  2984.     Exit:    function result = error code.
  2985. ----------------------------------------------------------------------------*/
  2986.  
  2987. static OSErr DoExtractBinaries (WindowPtr wind, short modifiers)
  2988. {
  2989.     return ExtractBinariesForArticleWindow(wind, modifiers);
  2990. }
  2991.  
  2992.  
  2993.  
  2994. /*----------------------------------------------------------------------------
  2995.     DoOpenAllReferences
  2996.     
  2997.     Handle the "Open all References" command.
  2998.     
  2999.     Entry:    wind = pointer to article window.
  3000.     
  3001.     Exit:    function result = error code.
  3002. ----------------------------------------------------------------------------*/
  3003.  
  3004. static OSErr DoOpenAllReferences (WindowPtr wind)
  3005. {
  3006.     WindowPtr newWind;
  3007.     TWindow **info;
  3008.     Handle fullText, references = nil;
  3009.     CStr255 msgId;
  3010.     short totalRefs = 0, totalOpenedRefs = 0;
  3011.     char *p, *q;
  3012.     long len;
  3013.     OSErr err = noErr;
  3014.     
  3015.     info = (TWindow**)GetWRefCon(wind);
  3016.     fullText = (**info).fullText;
  3017.     
  3018.     err = FindHeaderHandle(fullText, "References", &references);
  3019.     if (err != noErr) goto exit;
  3020.     if (references == nil) {
  3021.         NoteMessageNumber(kStrNoRefs);
  3022.         return noErr;
  3023.     }
  3024.  
  3025.     MyHLock(references);
  3026.     q = *references + MyGetHandleSize(references) - 1;
  3027.     while (q >= *references) {
  3028.         while (*q != '>' && q >= *references) q--;
  3029.         p = q-1;
  3030.         while (*p != '<' && p >= *references) p--;
  3031.         len = q - p + 1;
  3032.         q = p - 1;
  3033.         if (len <= 2 || len > 255) continue;
  3034.         BlockMoveData(p, msgId, len);
  3035.         *(msgId + len) = 0;
  3036.         totalRefs++;
  3037.         if (CheckAlreadyOpen(msgId)) {
  3038.             totalOpenedRefs++;
  3039.         } else {
  3040.             err = GetArticleAndMakeNewWindow(nil, 0, nil, 0, msgId, nil, false, nil, true, 
  3041.                 &newWind);
  3042.             if (err != noErr) goto exit;
  3043.             if (newWind != nil) {
  3044.                 totalOpenedRefs++;
  3045.                 MyShowWindow(newWind);
  3046.             }
  3047.         }
  3048.     }
  3049.     MyDisposeHandle(references);
  3050.  
  3051.     if (totalOpenedRefs < totalRefs) {
  3052.         if (totalOpenedRefs == 0) {
  3053.             NoteMessageNumber(kStrNoneOpened);
  3054.         } else {
  3055.             NoteMessageNumber(kStrSomeNotOpened);
  3056.         }
  3057.     }
  3058.     return noErr;
  3059.  
  3060. exit:
  3061.  
  3062.     MyDisposeHandle(references);
  3063.     return err;
  3064. }
  3065.  
  3066.  
  3067.  
  3068. /*----------------------------------------------------------------------------
  3069.     DoCancelArticle 
  3070.     
  3071.     Handles the "Cancel Article" command.
  3072.     
  3073.     Entry:    wind = pointer to article window.
  3074.     
  3075.     Exit:    function result = error code.
  3076. ----------------------------------------------------------------------------*/
  3077.  
  3078. static OSErr DoCancelArticle (WindowPtr wind)
  3079. {
  3080.     TWindow **info;
  3081.     Handle text;
  3082.     Handle groups = nil;
  3083.     CStr255 idStr, statusMsg;
  3084.     OSErr err = noErr;
  3085.     
  3086.     info = (TWindow**)GetWRefCon(wind);
  3087.     text = (**info).fullText;
  3088.     if (!UserIsPoster(text)) goto exit1;
  3089.  
  3090.     GetCString(kStrCancelingStatusMsg, statusMsg);
  3091.     err = DisplayStatusMessage(statusMsg);
  3092.     if (err != noErr) return err;
  3093.     
  3094.     if (!FindHeaderCString(text, "Message-ID", idStr, sizeof(idStr))) goto exit2;
  3095.     
  3096.     err = FindHeaderHandle(text, "Newsgroups", &groups);
  3097.     if (err != noErr) goto exit3;
  3098.     if (groups == nil) goto exit4;
  3099.     
  3100.     err = CancelArticle(idStr, groups, statusMsg);
  3101.     if (err != noErr) goto exit3;
  3102.     
  3103.     MyDisposeHandle(groups);
  3104.     return noErr;
  3105.     
  3106. exit1:
  3107.  
  3108.     MyDisposeHandle(groups);
  3109.     ErrorMessageNumber(kStrNotCanceled);
  3110.     return userCanceledErr;
  3111.     
  3112. exit2:
  3113.  
  3114.     MyDisposeHandle(groups);
  3115.     ErrorMessageNumber(kStrNoMsgID);
  3116.     return userCanceledErr;
  3117.     
  3118. exit3:
  3119.  
  3120.     MyDisposeHandle(groups);
  3121.     return err;
  3122.     
  3123. exit4:
  3124.  
  3125.     MyDisposeHandle(groups);
  3126.     ErrorMessageNumber(kStrNoNewsgroupsHeader);
  3127.     return userCanceledErr;
  3128. }
  3129.  
  3130.  
  3131.  
  3132.  
  3133. /*----------------------------------------------------------------------------
  3134.     Activate 
  3135.     
  3136.     Handle an activate event for an article window.
  3137.             
  3138.     Entry:    wind = pointer to article window.
  3139.             act = true to activate, false to deactivate
  3140. ----------------------------------------------------------------------------*/
  3141.  
  3142. static void Activate (WindowPtr wind, Boolean act)
  3143. {
  3144.     TWindow **info;
  3145.     TEHandle theTE;
  3146.     ControlHandle vScroll, hScroll;
  3147.     Rect r;
  3148.  
  3149.     info = (TWindow**)GetWRefCon(wind);
  3150.     theTE = (**info).theTE;
  3151.     vScroll = (**info).vScroll;
  3152.     hScroll = (**info).hScroll;
  3153.     if (act) {
  3154.         ShowControl(vScroll);
  3155.         if (hScroll != nil) ShowControl(hScroll);
  3156.         TEActivate(theTE);
  3157.     } else {
  3158.         HideControl(vScroll);
  3159.         if (hScroll != nil) HideControl(hScroll);
  3160.         TEDeactivate(theTE);
  3161.     }
  3162.     r = wind->portRect;
  3163.     r.top = r.bottom - 15;
  3164.     r.left = r.right - 15;
  3165.     InvalRect(&r);
  3166. }
  3167.  
  3168.  
  3169.  
  3170. /*----------------------------------------------------------------------------
  3171.     Update 
  3172.     
  3173.     Handle an update event for an article window.
  3174.             
  3175.     Entry:    wind = pointer to article window.
  3176. ----------------------------------------------------------------------------*/
  3177.  
  3178. static void Update (WindowPtr wind)
  3179. {
  3180.     TWindow **info, **parentInfo;
  3181.     short panelHeight, windWidth, lineHeight;
  3182.     Rect r;
  3183.     TEHandle theTE;
  3184.     WindowPtr parent;
  3185.     TSubject subject, **subjectArray;
  3186.     Handle fullText;
  3187.     CStr255 from, organization, date, newsgroups;
  3188.     CStr255 followupto, replyto;
  3189.     CStr255 articleXofYFormat;
  3190.     char msg[600];
  3191.     short len, threadInfoWidth, v;
  3192.     char c;
  3193.     Rect iconRect;
  3194.     short maxPanelStringWidth;
  3195.     FontInfo fontInfo;
  3196.  
  3197.     info = (TWindow**)GetWRefCon(wind);
  3198.     panelHeight = (**info).panelHeight;
  3199.     theTE = (**info).theTE;
  3200.     fullText = (**info).fullText;
  3201.     fontInfo = (**info).fontInfo;
  3202.     lineHeight = (**info).lineHeight;
  3203.     if (lineHeight < 11) lineHeight = 11;
  3204.     
  3205.     r = wind->portRect;
  3206.     r.top += panelHeight;
  3207.     ClipRect(&r);
  3208.     DrawGrowIcon(wind);
  3209.     NoClip();
  3210.     
  3211.     UpdateControls(wind, wind->visRgn);
  3212.     
  3213.     windWidth = wind->portRect.right - wind->portRect.left;
  3214.     MoveTo(0, panelHeight-3);
  3215.     LineTo(windWidth, panelHeight-3);
  3216.     MoveTo(0, panelHeight-1);
  3217.     LineTo(windWidth, panelHeight-1);
  3218.     
  3219.     if (CanExtractBinaries(wind, &iconRect)) {
  3220.         PlotIconID(&iconRect, 0, ttNone, kFileIconID);
  3221.         maxPanelStringWidth = iconRect.left - 20;
  3222.     } else if ((**info).parentWindow != nil) {
  3223.         maxPanelStringWidth = windWidth - 40;
  3224.     } else {
  3225.         maxPanelStringWidth = windWidth - 20;
  3226.     }
  3227.     
  3228.     if ((**info).parentWindow != nil) DrawArrowPair((**info).nextPrevArrowPair);
  3229.         
  3230.     FindHeaderCString(fullText, "From", from, sizeof(from));
  3231.     GetCString(kStrFrom, msg);
  3232.     strcat(msg, from);
  3233.     v = fontInfo.ascent + 3;
  3234.     DrawPanelString(msg, v, maxPanelStringWidth);
  3235.     
  3236.     FindHeaderCString(fullText, "Organization", organization, sizeof(organization));
  3237.     GetCString(kStrOrg, msg);
  3238.     strcat(msg, organization);
  3239.     v += lineHeight;
  3240.     DrawPanelString(msg, v, maxPanelStringWidth);
  3241.     
  3242.     FindHeaderCString(fullText, "NewsGroups", newsgroups, sizeof(date));
  3243.     GetCString(kStrNewsgroups, msg);
  3244.     strcat(msg, newsgroups);
  3245.     v += lineHeight;
  3246.     DrawPanelString(msg, v, maxPanelStringWidth);
  3247.     
  3248.     if ((**info).showFollowupTo) {
  3249.         FindHeaderCString(fullText, "Followup-To", followupto, sizeof(followupto));
  3250.         GetCString(kStrFollowupto, msg);
  3251.         strcat(msg, followupto);
  3252.         v += lineHeight;
  3253.         DrawPanelString(msg, v, maxPanelStringWidth);
  3254.     }
  3255.     
  3256.     if ((**info).showReplyTo) {
  3257.         FindHeaderCString(fullText, "Reply-To", replyto, sizeof(replyto));
  3258.         GetCString(kStrReplyto, msg);
  3259.         strcat(msg, replyto);
  3260.         v += lineHeight;
  3261.         DrawPanelString(msg, v, maxPanelStringWidth);
  3262.     }
  3263.  
  3264.     v += lineHeight;
  3265.     threadInfoWidth = 0;
  3266.     parent = (**info).parentWindow;
  3267.     if (parent != nil) {
  3268.         parentInfo = (TWindow**)GetWRefCon(parent);
  3269.         subjectArray = (**parentInfo).subjectArray;
  3270.         subject = (*subjectArray)[(**info).parentSubject];
  3271.         if (subject.threadLength > 1) {
  3272.             c = subject.threadOrdinal == subject.threadLength ? '•' : '…';
  3273.             GetCString(kStrArticleXofY, articleXofYFormat);
  3274.             sprintf(msg, articleXofYFormat, subject.threadOrdinal, subject.threadLength, c);
  3275.             len = strlen(msg);
  3276.             threadInfoWidth = TextWidth(msg, 0, len);
  3277.             MoveTo(windWidth - 10 - threadInfoWidth, v);
  3278.             DrawText(msg, 0, len);
  3279.         }
  3280.     }
  3281.     (**info).threadInfoWidth = threadInfoWidth;
  3282.  
  3283.     FindHeaderCString(fullText, "Date", date, sizeof(date));
  3284.     Cleanup822Date(date);
  3285.     GetCString(kStrDate, msg);
  3286.     strcat(msg, date);
  3287.     DrawPanelString(msg, v, windWidth-20-threadInfoWidth);
  3288.     
  3289.     if ((**info).numSections > 1) DrawSectionMessage(wind);
  3290.     
  3291.     TEUpdate(&wind->portRect, theTE);
  3292.     
  3293.     DrawPadlockIcon(wind);
  3294. }
  3295.  
  3296.  
  3297.  
  3298. /*----------------------------------------------------------------------------
  3299.     Mouse 
  3300.     
  3301.     Handle a mouse down event in the content area of an article window.
  3302.             
  3303.     Entry:    wind = pointer to article window.
  3304.             where = location of mouse down in local coords.
  3305.             modifiers = modifiers field from event record.
  3306.             
  3307.     Exit:    function result = error code.
  3308. ----------------------------------------------------------------------------*/
  3309.  
  3310. static OSErr Mouse (WindowPtr wind, Point where, short modifiers)
  3311. {
  3312.     TWindow **info;
  3313.     TEHandle theTE;
  3314.     Rect viewRect, iconRect;
  3315.     short part, oldVal, dv,dh;
  3316.     ControlHandle control, vScroll, hScroll;
  3317.     OSErr err = noErr;
  3318.     Boolean dragged = false, trashed;
  3319.     short oldSelStart, oldSelEnd, trackArrowPairResult;
  3320.     Boolean Draggable (WindowPtr wind, Point where, short modifiers);
  3321.     
  3322.     if (HandlePadlockClick(wind, where, &gPrefs.articleWindPosLocked, 
  3323.         &gPrefs.articleWindLocn)) return noErr;
  3324.  
  3325.     info = (TWindow**) GetWRefCon(wind);
  3326.     theTE = (**info).theTE;
  3327.     vScroll = (**info).vScroll;
  3328.     hScroll = (**info).hScroll;
  3329.     
  3330.     if ((**info).sectionArrowPair != nil) {
  3331.         trackArrowPairResult = TrackArrowPair((**info).sectionArrowPair);
  3332.         if (trackArrowPairResult == -1) {
  3333.             ScrollActionSection(hScroll, inUpButton);
  3334.             return noErr;
  3335.         } else if (trackArrowPairResult == +1) {
  3336.             ScrollActionSection(hScroll, inDownButton);
  3337.             return noErr;
  3338.         }
  3339.     }
  3340.     
  3341.     if ((**info).nextPrevArrowPair != nil) {
  3342.         trackArrowPairResult = TrackArrowPair((**info).nextPrevArrowPair);
  3343.         if (trackArrowPairResult != 0) {
  3344.             return GoBackwardsOrForwardsOneArticle(wind, trackArrowPairResult);
  3345.         }
  3346.     }
  3347.     
  3348.     viewRect = (**theTE).viewRect;
  3349.     InsetRect(&viewRect, -kTextMargin, 0);
  3350.     part = FindControl(where, wind, &control);
  3351.     if (part != 0) {
  3352.         if (control == vScroll) {
  3353.             if (part == inThumb) {
  3354.                 oldVal = GetControlValue(vScroll);
  3355.                 TrackControl(vScroll, where, nil);
  3356.                 dv = GetControlValue(vScroll) - oldVal;
  3357.                 if (dv != 0) TEScrollScrollText(theTE, vScroll, -dv);
  3358.             } else {
  3359.                 SetControlReference(vScroll, 0);
  3360.                 TrackControl(vScroll, where, gScrollActionUPP);
  3361.                 SetControlReference(vScroll, 1);
  3362.                 TEScrollAdjustScrollMax(theTE, vScroll);
  3363.             }
  3364.         } else if (control == hScroll) {
  3365.             if (part == inThumb) {
  3366.                 oldVal = GetControlValue(hScroll);
  3367.                 TrackControl(hScroll, where, nil);
  3368.                 dh = GetControlValue(hScroll) - oldVal;
  3369.                 if (dh != 0) ScrollSection(wind, -dh);
  3370.             } else {
  3371.                 TrackControl(hScroll, where, gScrollActionSectionUPP);
  3372.             }
  3373.         }
  3374.     } else if (PtInRect(where, &viewRect)) {
  3375.         if (gHaveDragMgr) {
  3376.             err = DragText(&gCurEvent, where, theTE, &dragged, &trashed);
  3377.             if (err != noErr) return err;
  3378.         }
  3379.         if (wind == FrontWindow() && !dragged) {
  3380.             oldSelStart = (**theTE).selStart;
  3381.             oldSelEnd = (**theTE).selEnd;
  3382.             MyTEClick(where, (modifiers & shiftKey) != 0, theTE);
  3383.             err = CommandClick(wind, theTE, oldSelStart, oldSelEnd, modifiers);
  3384.             if (err != noErr) return err;
  3385.         }
  3386.      } else if (CanExtractBinaries(wind, &iconRect) && PtInRect(where, &iconRect) &&
  3387.          Draggable(wind, where, 0)) 
  3388.      {
  3389.          if (gHaveDragMgr) {
  3390.             if ((**info).parentWindow == nil) return TooStupidAlert();
  3391.              return DragAttachedFileIcon(wind, &iconRect, modifiers);
  3392.          } else if (TrackIconClick(where, &iconRect, nil, kFileIconID)) {
  3393.              return ExtractBinariesForArticleWindow(wind, modifiers);
  3394.          }
  3395.      }
  3396.      
  3397.      return noErr;
  3398. }
  3399.  
  3400.  
  3401.  
  3402. /*----------------------------------------------------------------------------
  3403.     Draggable
  3404.     
  3405.     Determine whether a mouse down event is on a draggable object in an 
  3406.     article window.
  3407.     
  3408.     Entry:    wind = pointer to article window.
  3409.             where = location of mouse down event, in local coordinates.
  3410.             modifiers = modifiers field from event record.
  3411.             
  3412.     Exit:    function result = true if object is draggable.
  3413. ----------------------------------------------------------------------------*/
  3414.  
  3415. static Boolean Draggable (WindowPtr wind, Point where, short modifiers)
  3416. {
  3417.     Rect iconRect;
  3418.     RgnHandle iconRgn;
  3419.     OSErr err = noErr;
  3420.     Boolean result;
  3421.     TWindow **info;
  3422.     TEHandle theTE;
  3423.  
  3424.     if (CanExtractBinaries(wind, &iconRect)) {
  3425.         iconRgn = NewRgn();
  3426.         err = IconIDToRgn(iconRgn, &iconRect, ttNone, kFileIconID);
  3427.         result = err == noErr && PtInRgn(where, iconRgn);
  3428.         DisposeRgn(iconRgn);
  3429.         if (result) return true;
  3430.     }
  3431.     info = (TWindow**)GetWRefCon(wind);
  3432.     theTE = (**info).theTE;
  3433.     return PtInTEHiliteRgn(where, theTE);
  3434. }
  3435.  
  3436.  
  3437.  
  3438. /*----------------------------------------------------------------------------
  3439.     Key 
  3440.     
  3441.     Handle a key down event for an article window.
  3442.             
  3443.     Entry:    wind = pointer to article window.
  3444.             theChar = ASCII code of key.
  3445.             theKey = keyboard code of key.
  3446.             modifiers = modifiers field from event record.
  3447.             
  3448.     Exit:    function result = error code.
  3449. ----------------------------------------------------------------------------*/
  3450.  
  3451. static OSErr Key (WindowPtr wind, unsigned char theChar, unsigned char theKey, 
  3452.     short modifiers)
  3453. {
  3454.     TWindow **info;
  3455.     ControlHandle vScroll, hScroll;
  3456.     TEHandle theTE;
  3457.     OSErr err = noErr;
  3458.     short scrollIntoView;
  3459.     TKeypadKey keypadKey;
  3460.     Boolean isArrow, first, last;
  3461.  
  3462.     info = (TWindow**)GetWRefCon(wind);
  3463.     vScroll = (**info).vScroll;
  3464.     hScroll = (**info).hScroll;
  3465.     theTE = (**info).theTE;
  3466.     isArrow = IsArrowKey(theChar);
  3467.     
  3468.     if ((modifiers & cmdKey) != 0 && !isArrow) {
  3469.         if ((**info).parentWindow != nil) {
  3470.             CheckFirstLastArticle(wind, &first, &last);
  3471.             if (theChar == '1') {
  3472.                 if (first) {
  3473.                     SysBeep(0);
  3474.                     return noErr;
  3475.                 }
  3476.                 return GoBackwardsOrForwardsOneArticle(wind, -1);
  3477.             } else if (theChar == '2') {
  3478.                 if (last) {
  3479.                     SysBeep(0);
  3480.                     return noErr;
  3481.                 }
  3482.                 return GoBackwardsOrForwardsOneArticle(wind, +1);
  3483.             }
  3484.         }
  3485.         SysBeep(0);
  3486.         return noErr;
  3487.     }
  3488.  
  3489.     if (gPrefs.keypadShortcuts && IsKeypadKey(theChar, theKey, &keypadKey)) {
  3490.         switch (keypadKey) {
  3491.             case kKeypadEqualKey:
  3492.                 DoSelectAll(wind);
  3493.                 return noErr;
  3494.             case kKeypadStarKey:
  3495.                 return DoClose(wind);
  3496.             case kKeypadMinusKey:
  3497.                 return DoMarkCommand(wind, false);
  3498.             case kKeypadPlusKey:
  3499.                 return DoMarkCommand(wind, true);
  3500.             case kKeypadEnterKey:
  3501.                 return DoNextGroup(wind);
  3502.             case kKeypadPeriodKey:
  3503.                 return DoNextThread(wind);
  3504.             case kKeypad0Key:
  3505.                 return DoNextArticle(wind);
  3506.             case kKeypad1Key:
  3507.                 TEScrollScrollByPartCode(theTE, vScroll, kScrollToEnd);
  3508.                 return noErr;
  3509.             case kKeypad2Key:
  3510.                 TEScrollScrollByPartCode(theTE, vScroll, inDownButton);
  3511.                 return noErr;
  3512.             case kKeypad3Key:
  3513.                 TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  3514.                 return noErr;
  3515.             case kKeypad4Key:
  3516.                 if (hScroll != nil) {
  3517.                     ScrollActionSection(hScroll, inUpButton);
  3518.                 } else {
  3519.                     SysBeep(0);
  3520.                 }
  3521.                 return noErr;
  3522.             case kKeypad5Key:
  3523.                 if (GetControlValue(vScroll) < GetControlMaximum(vScroll)) {
  3524.                     TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  3525.                 } else if (hScroll != nil && 
  3526.                     GetControlValue(hScroll) < GetControlMaximum(hScroll)) 
  3527.                 {
  3528.                     ScrollActionSection(hScroll, inDownButton);
  3529.                 } else {
  3530.                     return DoNextArticle(wind);
  3531.                 }
  3532.                 return noErr;
  3533.             case kKeypad6Key:
  3534.                 if (hScroll != nil) {
  3535.                     ScrollActionSection(hScroll, inDownButton);
  3536.                 } else {
  3537.                     SysBeep(0);
  3538.                 }
  3539.                 return noErr;
  3540.             case kKeypad7Key:
  3541.                 TEScrollScrollByPartCode(theTE, vScroll, kScrollToHome);
  3542.                 return noErr;
  3543.             case kKeypad8Key:
  3544.                 TEScrollScrollByPartCode(theTE, vScroll, inUpButton);
  3545.                 return noErr;
  3546.             case kKeypad9Key:
  3547.                 TEScrollScrollByPartCode(theTE, vScroll, inPageUp);
  3548.                 return noErr;
  3549.             default:
  3550.                 SysBeep(0);
  3551.                 return noErr;
  3552.         }
  3553.     }
  3554.     
  3555.     if (theChar == pageUpKey) {
  3556.         TEScrollScrollByPartCode(theTE, vScroll, inPageUp);
  3557.         return noErr;
  3558.     }
  3559.     if (theChar == pageDownKey) {
  3560.         TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  3561.         return noErr;
  3562.     }
  3563.     if (theChar == homeKey) {
  3564.         TEScrollScrollByPartCode(theTE, vScroll, kScrollToHome);
  3565.         return noErr;
  3566.     }
  3567.     if (theChar == endKey) {
  3568.         TEScrollScrollByPartCode(theTE, vScroll, kScrollToEnd);
  3569.         return noErr;
  3570.     }
  3571.     
  3572.     if (isArrow) {
  3573.         TEArrowKey(theChar, modifiers, theTE, 0, &gPrevEvent, &scrollIntoView);
  3574.         TEScrollScrollRangeIntoView(theTE, scrollIntoView, scrollIntoView, vScroll);
  3575.         return noErr;
  3576.     }
  3577.     
  3578.     if (gPrefs.keyboardShortcuts) {
  3579.     
  3580.         if (theChar == ' ') {
  3581.             if (GetControlValue(vScroll) < GetControlMaximum(vScroll)) {
  3582.                 TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  3583.             } else if (hScroll != nil && GetControlValue(hScroll) < GetControlMaximum(hScroll)) {
  3584.                 ScrollActionSection(hScroll, inDownButton);
  3585.             } else {
  3586.                 return DoNextArticle(wind);
  3587.             }
  3588.             return noErr;
  3589.         }
  3590.         
  3591.         theChar = tolower(theChar);
  3592.         
  3593.         if (theChar == 'n' || theChar == 'i') {
  3594.             return DoNextArticle(wind);
  3595.         }
  3596.         
  3597.         if (theChar == 't') {
  3598.             return DoNextThread(wind);
  3599.         }
  3600.         if (theChar == 'g' || theChar == 'j') {
  3601.             return DoNextGroup(wind);
  3602.         }
  3603.         
  3604.         if (theChar == 'w') {
  3605.             return DoClose(wind);
  3606.         }
  3607.         
  3608.         if (theChar == 'u') {
  3609.             return DoMarkCommand(wind, false);
  3610.         }
  3611.         
  3612.         if (theChar == 'm') {
  3613.             return DoMarkCommand(wind, true);
  3614.         }
  3615.         
  3616.         if (theChar == 'a') {
  3617.             DoSelectAll(wind);
  3618.             return noErr;
  3619.         }
  3620.         
  3621.     }
  3622.  
  3623.     SysBeep(0);
  3624.     
  3625.     return noErr;
  3626. }
  3627.  
  3628.  
  3629.  
  3630. /*----------------------------------------------------------------------------
  3631.     Grow 
  3632.     
  3633.     Handle a mouse down event in the grow box of an article window.
  3634.     
  3635.     Entry:    wind = pointer to article window.
  3636.             where = location of mouse down event, in global coordinates.
  3637.             
  3638.     Exit:    function result = error code.
  3639. ----------------------------------------------------------------------------*/
  3640.  
  3641. static OSErr Grow (WindowPtr wind, Point where)
  3642. {
  3643.     Rect sizeRect;
  3644.     long size;
  3645.     short width, height;
  3646.     
  3647.     SetRect(&sizeRect, kMinWindowWidth, MinHeight(wind), kMaxShort, kMaxShort);
  3648.     size = GrowWindow(wind, where, &sizeRect);
  3649.     
  3650.     if (size  != 0) {
  3651.         width = LoWord(size);
  3652.         height = HiWord(size);
  3653.         FixHeight(wind, &height);
  3654.         SizeWindow(wind, width, height, false);
  3655.         ResizeContents(wind);
  3656.     }
  3657.     
  3658.     return noErr;
  3659. }
  3660.  
  3661.  
  3662.  
  3663. /*----------------------------------------------------------------------------
  3664.     Zoom
  3665.     
  3666.     Zoom an article window.
  3667.     
  3668.     Entry:    wind = pointer to article window.
  3669.             zoomDir = zoom direction = inZoomIn, inZoomOut, or
  3670.                 zoomOutOnlyIfBigger.
  3671.             
  3672.     Exit:    function result = error code.
  3673. ----------------------------------------------------------------------------*/
  3674.  
  3675. static OSErr Zoom (WindowPtr wind, short zoomDir)
  3676. {
  3677.     TWindow **info;
  3678.     short width, height, lineHeight, minHeight, lineWidth, maxWidth, maxBodyLineWidth;
  3679.     Rect zoomRect, r;    
  3680.     WStateData **wState;
  3681.     TEHandle theTE, tempTE = nil;
  3682.     Handle fullText;
  3683.     long numBodyLines, longHeight, offset, length, maxHeight;
  3684.     char *p, *pEnd, *lineStart;
  3685.     long **breaks;
  3686.     short numSections, i;
  3687.     OSErr err = noErr;
  3688.     char state;
  3689.     FontInfo fontInfo;
  3690.  
  3691.     wState = (WStateData**)((WindowPeek)wind)->dataHandle;
  3692.     if (zoomDir == inZoomOut || zoomDir == zoomOutOnlyIfBigger) {
  3693.         info = (TWindow**)GetWRefCon(wind);
  3694.         fontInfo = (**info).fontInfo;
  3695.         lineHeight = (**info).lineHeight;
  3696.         theTE = (**info).theTE;
  3697.         fullText = (**info).fullText;
  3698.         maxBodyLineWidth = (**info).maxBodyLineWidth;
  3699.         numBodyLines = (**info).numBodyLines;
  3700.         
  3701.         if (maxBodyLineWidth == 0) {
  3702.             maxWidth = 80 * fontInfo.widMax;
  3703.             numBodyLines = 0;
  3704.             state = MyHGetState(fullText);
  3705.             MyHLock(fullText);
  3706.             for (p = *fullText + FindBody(fullText), 
  3707.                     pEnd = *fullText + MyGetHandleSize(fullText); 
  3708.                 p < pEnd; 
  3709.                 p++) 
  3710.             {
  3711.                 lineStart = p;
  3712.                 while (p < pEnd && *p != CR) p++;
  3713.                 numBodyLines++;
  3714.                 if (maxBodyLineWidth < maxWidth) {
  3715.                     lineWidth = TextWidth(lineStart, 0, p - lineStart);
  3716.                     if (lineWidth > maxBodyLineWidth) maxBodyLineWidth = lineWidth;
  3717.                 }
  3718.             }
  3719.             MyHSetState(fullText, state);
  3720.             if (maxBodyLineWidth > maxWidth) maxBodyLineWidth = maxWidth;
  3721.             (**info).maxBodyLineWidth = maxBodyLineWidth;
  3722.             (**info).numBodyLines = numBodyLines;
  3723.         }
  3724.         width = maxBodyLineWidth + 2*kTextMargin + 18;
  3725.         if (width < kMinWindowWidth) width = kMinWindowWidth;
  3726.         
  3727.         if (numBodyLines > 100) {
  3728.             height = kMaxShort;
  3729.         } else {
  3730.             CalculateZoomRect(wind, width, kMaxShort, &zoomRect, gPrefs.dontCoverFinderIcons);
  3731.             SetRect(&r, 0, 0, zoomRect.right - zoomRect.left - 15 - 2*kTextMargin, kMaxShort);
  3732.             tempTE = TENew(&r, &r);
  3733.             breaks = (**info).sectionBreaks;
  3734.             numSections = (**info).numSections;
  3735.             maxHeight = 0;
  3736.             for (i = 0; i < numSections; i++) {
  3737.                 offset = (*breaks)[i];
  3738.                 length = (*breaks)[i+1] - offset;
  3739.                 if (i == 0 && !(**info).showDetails) {
  3740.                     offset = FindBody(fullText);
  3741.                     if (offset > length) offset = length;
  3742.                     length -= offset;
  3743.                 }
  3744.                 state = MyHGetState(fullText);
  3745.                 MyHLock(fullText);
  3746.                 err = MyTESetText(*fullText + offset, length, tempTE);
  3747.                 MyHSetState(fullText, state);
  3748.                 if (err != noErr) goto exit;
  3749.                 longHeight = (long)TEScrollNumTELines(tempTE) * (long)lineHeight;
  3750.                 longHeight += (**info).panelHeight + 15 + 2*kTextMargin;
  3751.                 if (longHeight > maxHeight) maxHeight = longHeight;
  3752.             }
  3753.             TEDispose(tempTE);
  3754.             if (maxHeight > kMaxShort) {
  3755.                 height = kMaxShort;
  3756.             } else {
  3757.                 height = maxHeight;
  3758.                 minHeight = MinHeight(wind);
  3759.                 if (height < minHeight) height = minHeight;
  3760.             }
  3761.         }
  3762.         
  3763.         if (zoomDir == zoomOutOnlyIfBigger) {
  3764.             if (width <= wind->portRect.right && height <= wind->portRect.bottom) {
  3765.                 SetWindowNeedsZooming(wind);
  3766.                 return noErr;
  3767.             }
  3768.             if (width < wind->portRect.right) width = wind->portRect.right;
  3769.             if (height < wind->portRect.bottom) height = wind->portRect.bottom;
  3770.         }
  3771.         
  3772.         CalculateZoomRect(wind, width, height, &zoomRect, gPrefs.dontCoverFinderIcons);
  3773.         height = zoomRect.bottom - zoomRect.top;
  3774.         FixHeight(wind, &height);
  3775.         zoomRect.bottom = zoomRect.top + height;
  3776.         (**wState).stdState = zoomRect;
  3777.         if (WindRectEqualRect(wind, &zoomRect)) return noErr;
  3778.     }
  3779.     
  3780.     EraseRect(&wind->portRect);
  3781.     ZoomWindow(wind, zoomDir, false);
  3782.     ResizeContents(wind);
  3783.     return noErr;
  3784.     
  3785. exit:
  3786.  
  3787.     if (tempTE != nil) TEDispose(tempTE);
  3788.     return err;
  3789. }
  3790.  
  3791.  
  3792.  
  3793. /*----------------------------------------------------------------------------
  3794.     Command 
  3795.     
  3796.     Handle a command for an article window.
  3797.             
  3798.     Entry:    wind = pointer to article window.
  3799.             menu = the menu.
  3800.             item = the item.
  3801.             modifiers = modifiers field from event record.
  3802.     
  3803.     Exit:    function result = error code.
  3804. ----------------------------------------------------------------------------*/
  3805.  
  3806. static OSErr Command (WindowPtr wind, short menu, short item, short modifiers)
  3807. {
  3808.     OSErr err = noErr;
  3809.  
  3810.     switch (menu) {
  3811.                 
  3812.         case kFileMenu:
  3813.         
  3814.             switch (item) {
  3815.                 case kSaveItem:
  3816.                     err = DoSave(wind, modifiers);
  3817.                     break;
  3818.                 case kSaveAsItem:
  3819.                     err = DoSaveAs(wind, modifiers);
  3820.                     break;
  3821.                 case kAppendItem:
  3822.                     err = DoAppend(wind, modifiers);
  3823.                     break;
  3824.                 case kPrintItem:
  3825.                     err = DoPrint(wind, modifiers);
  3826.                     break;
  3827.             }
  3828.             break;
  3829.             
  3830.         case kEditMenu:
  3831.  
  3832.             switch (item) {
  3833.                 case kCopyItem:
  3834.                     DoCopy(wind);
  3835.                     break;
  3836.                 case kSelectAllItem:
  3837.                     DoSelectAll(wind);
  3838.                     break;
  3839.                 case kFindItem:
  3840.                     err = DoFind(wind);
  3841.                     break;
  3842.                 case kFindAgainItem:
  3843.                     err = DoFindAgain(wind);
  3844.                     break;
  3845.                 case kEnterSelectionItem:
  3846.                     err = DoEnterSelection(wind);
  3847.                     break;
  3848.                 case kShowHideDetailsItem:
  3849.                     err = DoShowHideDetails(wind);
  3850.                     break;
  3851.                 case kRot13Item:
  3852.                     DoRot13(wind);
  3853.                     break;
  3854.             }
  3855.             break;
  3856.  
  3857.         case kNewsMenu:
  3858.         
  3859.             switch (item) {
  3860.                 case kNextArticleItem:
  3861.                     err = DoNextArticle(wind);
  3862.                     break;
  3863.                 case kNextThreadItem:
  3864.                     err = DoNextThread(wind);
  3865.                     break;
  3866.                 case kNextGroupItem:
  3867.                     err = DoNextGroup(wind);
  3868.                     break;
  3869.                 case kMarkReadItem:
  3870.                     err = DoMarkCommand(wind, true);
  3871.                     break;
  3872.                 case kMarkUnreadItem:
  3873.                     err = DoMarkCommand(wind, false);
  3874.                     break;
  3875.                 case kReplyItem:
  3876.                     err = DoReply(wind, modifiers);
  3877.                     break;
  3878.                 case kForwardItem:
  3879.                     err = DoForward(wind, modifiers);
  3880.                     break;
  3881.                 case kRedirectItem:
  3882.                     err = DoRedirect(wind, modifiers);
  3883.                     break;
  3884.             }
  3885.             break;
  3886.  
  3887.         case kSpecialMenu:
  3888.             switch (item) {
  3889.                 case kExtractBinariesItem:
  3890.                     err = DoExtractBinaries(wind, modifiers);
  3891.                     break;
  3892.                 case kSearchItem:
  3893.                     err = DoSearch(wind);
  3894.                     break;
  3895.                 case kOpenAllReferencesItem:
  3896.                     err = DoOpenAllReferences(wind);
  3897.                     break;
  3898.                 case kCancelArticleItem:
  3899.                     err = DoCancelArticle(wind);
  3900.                     break;
  3901.             }
  3902.             break;
  3903.      }
  3904.      
  3905.      return err;
  3906. }
  3907.  
  3908.  
  3909.  
  3910. /*----------------------------------------------------------------------------
  3911.     Close 
  3912.     
  3913.     Close an article window.
  3914.             
  3915.     Entry:    wind = pointer to article window.
  3916.     
  3917.     Exit:    function result = error code.
  3918. ----------------------------------------------------------------------------*/
  3919.  
  3920. static OSErr Close (WindowPtr wind)
  3921. {
  3922.     TWindow **info;
  3923.  
  3924.     info = (TWindow**)GetWRefCon(wind);
  3925.     
  3926.     RemoveChild((**info).parentWindow, wind);
  3927.     
  3928.     SaveArticleInCache(wind);
  3929.     
  3930.     if ((**info).theTE != nil) TEDispose((**info).theTE);
  3931.     MyDisposeHandle((**info).sectionBreaks);
  3932.     DisposeArrowPair((**info).sectionArrowPair);
  3933.     DisposeArrowPair((**info).nextPrevArrowPair);
  3934.     MyDisposeHandle(info);
  3935.  
  3936.     MyDisposeWindow(wind);
  3937.     
  3938.     return noErr;
  3939. }
  3940.  
  3941.  
  3942.  
  3943. /*----------------------------------------------------------------------------
  3944.     Idle 
  3945.     
  3946.     Handle idle time tasks for an article window.
  3947.             
  3948.     Entry:    wind = pointer to article window.
  3949.     
  3950.     Exit:    cursorRgn = cursor region for WaitNextEvent mouse moved events.
  3951. ----------------------------------------------------------------------------*/
  3952.  
  3953. static void Idle (WindowPtr wind, RgnHandle cursorRgn)
  3954. {
  3955.     TWindow **info;
  3956.     TEHandle theTE;
  3957.     Rect r;
  3958.     Point where;
  3959.     unsigned long newsEnabled, editEnabled, specialEnabled;
  3960.     CStr255 refs;
  3961.     short selStart, selEnd;
  3962.     Rect iconRect;
  3963.  
  3964.     info = (TWindow**)GetWRefCon(wind);
  3965.     theTE = (**info).theTE;
  3966.     TEIdle(theTE);
  3967.     
  3968.     GetTextRect(wind, &r);
  3969.     InsetRect(&r, -kTextMargin, 0);
  3970.     LocalToGlobalRect(&r);
  3971.     RectRgn(cursorRgn, &r);
  3972.     if (gHaveDragMgr) SubtractTEHiliteRgn(cursorRgn, theTE);
  3973.     GetMouse(&where);
  3974.     LocalToGlobal(&where);
  3975.     if (PtInRgn(where, cursorRgn)) {
  3976.         SetCursor(&gIBeamCurs);
  3977.     } else {
  3978.         SetCursor(&qd.arrow);
  3979.         ComplementRgn(cursorRgn);
  3980.     }
  3981.     
  3982.     newsEnabled = (**info).parentWindow == nil ? kArticleNewsEnabledNoParent :
  3983.         kArticleNewsEnabled;
  3984.     editEnabled = kArticleEditEnabled;
  3985.     specialEnabled = kArticleSpecialEnabled;
  3986.     selStart = (**theTE).selStart;
  3987.     selEnd = (**theTE).selEnd;
  3988.     if (selStart >= selEnd) {
  3989.         editEnabled &= ~(kCopyMask | kEnterSelectionMask);
  3990.     } else if (selEnd > selStart + 255) {
  3991.         editEnabled &= ~kEnterSelectionMask;
  3992.     }
  3993.     if ((**theTE).teLength == 0) editEnabled &= ~(kSelectAllMask | kRot13Mask);
  3994.     if (!FindHeaderCString((**info).fullText, "References", refs, sizeof(refs)))
  3995.         specialEnabled &= ~kOpenAllReferencesMask;
  3996.     /* Following line of code disabled because it can cause repeated disk hits
  3997.        with Internet Config.
  3998.         if (!UserIsPoster((**info).fullText)) specialEnabled &= ~kCancelArticleMask;
  3999.     */
  4000.     if (!CanExtractBinaries(wind, &iconRect)) specialEnabled &= ~kExtractBinariesMask;
  4001.     if (*gFindPattern == 0) editEnabled &= ~kFindAgainMask;
  4002.     SetMenusTo(kAppleAllEnabled, kArticleFileEnabled, editEnabled, 
  4003.         newsEnabled, specialEnabled, kArticleWindEnabled);
  4004.     SetEditMenuShowHideDetails(!(**info).showDetails);
  4005. }
  4006.  
  4007.  
  4008.  
  4009. /*----------------------------------------------------------------------------
  4010.     Help 
  4011.     
  4012.     Handle help balloons for an article window.
  4013.             
  4014.     Entry:    wind = pointer to article window.
  4015.             where = current mouse location in local coordinates.
  4016. ----------------------------------------------------------------------------*/
  4017.  
  4018. static void Help (WindowPtr wind, Point where)
  4019. {
  4020.     TWindow **info;
  4021.     Rect r;
  4022.     Point tip = {0,0};
  4023.     short panelHeight, result, lineHeight, threadInfoWidth;
  4024.     Boolean enabled, canExtractBinaries;
  4025.     ControlHandle hScroll;
  4026.     FontInfo fontInfo;
  4027.  
  4028.     if (DoSizeBoxAndVerticalScrollBarBalloons(wind, where)) return;
  4029.     info = (TWindow**)GetWRefCon(wind);
  4030.     panelHeight = (**info).panelHeight;
  4031.     threadInfoWidth = (**info).threadInfoWidth;
  4032.     fontInfo = (**info).fontInfo;
  4033.     lineHeight = (**info).lineHeight;
  4034.     if (lineHeight < 11) lineHeight = 11;
  4035.     SetRect(&r, 0, wind->portRect.bottom - 14, 15, wind->portRect.bottom);
  4036.     if (PtInRect(where, &r)) {
  4037.         ShowHelpBalloon(tip, &r, (**info).windPosLocked ? 26 : 25);
  4038.         return;
  4039.     }
  4040.     SetRect(&r, 0, panelHeight, 
  4041.         wind->portRect.right - 15, wind->portRect.bottom - 15);
  4042.     if (PtInRect(where, &r)) {
  4043.         ShowHelpBalloon(where, &r, 27);
  4044.         return;
  4045.     }
  4046.     result = TestArrowPairHotRect((**info).nextPrevArrowPair, where, &r, &enabled);
  4047.     if (result == -1) {
  4048.         ShowHelpBalloon(tip, &r, enabled ? 28 : 29);
  4049.         return;
  4050.     } else if (result == +1) {
  4051.         ShowHelpBalloon(tip, &r, enabled ? 30 : 31);
  4052.         return;
  4053.     }
  4054.     canExtractBinaries = CanExtractBinaries(wind, &r);
  4055.     if (canExtractBinaries) {
  4056.         r.right -= 11;
  4057.         r.top += 6;
  4058.         if (PtInRect(where, &r)) {
  4059.             ShowHelpBalloon(tip, &r, 32);
  4060.             return;
  4061.         }
  4062.     }
  4063.     r = wind->portRect;
  4064.     r.left = 5;
  4065.     r.top = 3;
  4066.     r.bottom = r.top + lineHeight;
  4067.     r.right -= 15;
  4068.     if ((**info).nextPrevArrowPair) r.right -= 12;
  4069.     if (canExtractBinaries) r.right -= 27;
  4070.     SetPt(&tip, 10, r.top + fontInfo.ascent);
  4071.     if (PtInRect(where, &r)) {
  4072.         ShowHelpBalloon(tip, &r, 33);
  4073.         return;
  4074.     }
  4075.     OffsetRect(&r, 0, lineHeight);
  4076.     tip.v += lineHeight;
  4077.     if (PtInRect(where, &r)) {
  4078.         ShowHelpBalloon(tip, &r, 34);
  4079.         return;
  4080.     }
  4081.     OffsetRect(&r, 0, lineHeight);
  4082.     tip.v += lineHeight;
  4083.     if (PtInRect(where, &r)) {
  4084.         ShowHelpBalloon(tip, &r, 35);
  4085.         return;
  4086.     }
  4087.     if ((**info).showFollowupTo) {
  4088.         OffsetRect(&r, 0, lineHeight);
  4089.         tip.v += lineHeight;
  4090.         if (PtInRect(where, &r)) {
  4091.             ShowHelpBalloon(tip, &r, 36);
  4092.             return;
  4093.         }
  4094.     }
  4095.     if ((**info).showReplyTo) {
  4096.         OffsetRect(&r, 0, lineHeight);
  4097.         tip.v += lineHeight;
  4098.         if (PtInRect(where, &r)) {
  4099.             ShowHelpBalloon(tip, &r, 37);
  4100.             return;
  4101.         }
  4102.     }
  4103.     OffsetRect(&r, 0, lineHeight);
  4104.     r.right = wind->portRect.right - threadInfoWidth - 15;
  4105.     tip.v += lineHeight;
  4106.     if (PtInRect(where, &r)) {
  4107.         ShowHelpBalloon(tip, &r, 38);
  4108.         return;
  4109.     }
  4110.     if (threadInfoWidth != 0) {
  4111.         r.left = wind->portRect.right - threadInfoWidth - 15;
  4112.         r.right = wind->portRect.right;
  4113.         if (PtInRect(where, &r)) {
  4114.             tip.h = r.left + 5;
  4115.             ShowHelpBalloon(tip, &r, 39);
  4116.         }
  4117.     }
  4118.     SetPt(&tip, 0, 0);
  4119.     if ((**info).numSections > 1) {
  4120.         SetRect(&r, 16, wind->portRect.bottom - 15,
  4121.             kSectionMargin - 30, wind->portRect.bottom);
  4122.         if (PtInRect(where, &r)) {
  4123.             ShowHelpBalloon(tip, &r, 40);
  4124.         }
  4125.         result = TestArrowPairHotRect((**info).sectionArrowPair, where, &r, &enabled);
  4126.         if (result == -1) {
  4127.             ShowHelpBalloon(tip, &r, enabled ? 41 : 42);
  4128.             return;
  4129.         } else if (result == +1) {
  4130.             ShowHelpBalloon(tip, &r, enabled ? 43 : 44);
  4131.             return;
  4132.         }
  4133.         hScroll = (**info).hScroll;
  4134.         if (hScroll != nil) {
  4135.             r = (**hScroll).contrlRect;
  4136.             if (PtInRect(where, &r)) {
  4137.                 ShowHelpBalloon(tip, &r, 45);
  4138.                 return;
  4139.             }
  4140.         }
  4141.     }
  4142. }
  4143.  
  4144.  
  4145.  
  4146. /*----------------------------------------------------------------------------
  4147.     InitArticleDispatchTable 
  4148.     
  4149.     Initialize the dispatch table for article windows.
  4150. ----------------------------------------------------------------------------*/
  4151.  
  4152. void InitArticleDispatchTable (void)
  4153. {
  4154.     TDispatch *d;
  4155.     
  4156.     d = &gDispatch[kArticle];
  4157.     
  4158.     d->activate = Activate;
  4159.     d->update = Update;
  4160.     d->mouse = Mouse;
  4161.     d->draggable = Draggable;
  4162.     d->key = Key;
  4163.     d->grow = Grow;
  4164.     d->zoom = Zoom;
  4165.     d->command = Command;
  4166.     d->close = Close;
  4167.     d->idle = Idle;
  4168.     d->help = Help;
  4169.     
  4170.     gAutoScrollUPP = NewTEClickLoopProc(AutoScroll);
  4171.     gDragAttachedFileIconSendProcUPP = NewDragSendDataProc(DragAttachedFileIconSendProc);
  4172.     gScrollActionUPP = NewControlActionProc(ScrollAction);
  4173.     gScrollActionSectionUPP = NewControlActionProc(ScrollActionSection);
  4174. }
  4175.